Files
nzbget/webui/lib/elycharts.js

3988 lines
165 KiB
JavaScript
Vendored
Raw Permalink Blame History

/********* Source File: extra-resources/header.js*********/
/*!*********************************************************************
* ELYCHARTS v2.1.6-SNAPSHOT (2014-02-19) $Id$
* A Javascript library to generate interactive charts with vectorial graphics.
*
* Copyright (c) 2010-2014 Void Labs s.n.c. (http://void.it)
* Licensed under the MIT (http://creativecommons.org/licenses/MIT/) license.
**********************************************************************/
/********* Source File: src/elycharts_defaults.js*********/
/**********************************************************************
* ELYCHARTS
* A Javascript library to generate interactive charts with vectorial graphics.
*
* Copyright (c) 2010-2014 Void Labs s.n.c. (http://void.it)
* Licensed under the MIT (http://creativecommons.org/licenses/MIT/) license.
**********************************************************************/
(function($) {
if (!$.elycharts)
$.elycharts = {};
/***********************************************************************
* DEFAULT OPTIONS
**********************************************************************/
$.elycharts.templates = {
common : {
// Tipo di grafico
// type : 'line|pie|funnel|barline'
// Permette di specificare una configurazione di default da utilizzare (definita in $.elycharts.templates.NOME)
// La configurazione completa <20> quindi data da tutti i valori della conf di default alla quale viene unita (con sovrascrittura) la conf corrente
// Il parametro <20> ricorsivo (la configurazione di default puo' a sua volta avere una configurazione di default)
// Se non specificato, la configurazione di default <20> quella con lo stesso nome del tipo di grafico
// template : 'NOME',
/* DATI:
// I valori associati a ogni serie del grafico. Ogni serie <20> associata a una chiave dell'oggetto value, il cui
// valore <20> l'array di dati relativi
values : {},
// Label associate ai valori del grafico
// Solo in caso di label gestite da labelmanager (quindi per pie e funnel) e per label.html = true e' possibile inserire
// degli elementi DOM/JQUERY che verranno presi e posizionati correttament.
labels : [],
// Anchor per la gestione mediante anchormanager. Possono essere stringhe e oggetti DOM/JQUERY che verranno riposizionati
anchors : {},
tooltips : {},
legend : [],
*/
// Autoresize uses jQuery resize event to automatically resize the chart to the container
// autoresize makes sense only when width or height is not defined.
// autoresize: false,
// Per impostare una dimensione diversa da quella del container settare width e height
//width : x,
//height : y
// I margini del grafico rispetto al frame complessivo. Da notare che riguardano la posizione del grafico
// principale, e NON degli elementi aggiuntivi (legenda, label e titoli degli assi...). Quindi i margini devono
// essere impostati in genere proprio per lasciare lo spazio per questi elementi
// Sintassi: [top, right, bottom, left]
margins: [10, 10, 10, 10],
// style : {},
// Per gestire al meglio l'interattivita' del grafico (tooltip, highlight, anchor...) viene inserito un secondo
// layer per le parti sensibili al mouse. Se si sa che il grafico non avra' alcuna interattivita' si puo' impostare
// questo valore a false per evitare di creare il layer (ottimizzando leggermente la pagina)
interactive : true,
// Dati da applicare a tutte le serie del grafico
defaultSeries : {
// Impostare a false per disabilitare la visualizzazione della serie
visible : true,
// Impostare color qui permette di impostare velocemente plotProps.stroke+fill, tooltip.frameProps.stroke, dotProps.stroke e fillProps.fill (se non specificati)
//color: 'blue',
//plotProps : { },
// Impostazioni dei tooltip
tooltip : {
active : true,
// Se width ed height vengono impostati a 0 o ad "auto" (equivalenti) non vengono fissate dimensioni, quindi il contenuto si autodimensiona in funzione del tooltip
// Impostare a 0|auto <20> incompatibile con il frame SVG, quindi viene automaticamente disabilitato (come se frameProps = false)
width: 100, height: 50,
roundedCorners: 5,
padding: [6, 6] /* y, x */,
offset: [20, 0] /* y, x */,
// Se frameProps = false non disegna la cornice del tooltip (ad es. per permettere di definire la propria cornice HTML)
frameProps : { fill: "white", "stroke-width": 2 },
contentStyle : { "font-family": "Arial", "font-size": "12px", "line-height": "16px", color: "black" }
},
// Highlight feature
highlight : {
// Cambia le dimensioni dell'elemento quando deve essere evidenziato
//scale : [x, y],
// Opzioni di animazione effetto "scale"
scaleSpeed : 100, scaleEasing : '',
// Cambia gli attributi dell'elemento quando evidenziato
//newProps : { opacity : 1 },
// Inserisce un layer con gli attributi specificati sopra quello da evidenziare
//overlayProps : {"fill" : "white", "fill-opacity" : .3, "stroke-width" : 0}
// Muove l'area evidenziata. E' possibile specificare un valore X o un array [X, Y]
//move : 10,
// Opzioni di animazione effetto "move"
moveSpeed : 100, moveEasing : '',
// Opzioni di animazione da usare per riportare l'oggetto alle situazione iniziale
restoreSpeed : 0, restoreEasing : ''
},
anchor : {
// Aggiunge alle anchor esterne la classe selezionata quando il mouse passa sull'area
//addClass : "",
// Evidenzia la serie al passaggio del mouse
//highlight : "",
// Se impostato a true usa gli eventi mouseenter/mouseleave invece di mouseover/mouseout per l'highlight
//useMouseEnter : false,
},
// Opzioni per la generazione animata dei grafici
startAnimation : {
//active : true,
type : 'simple',
speed : 600,
delay : 0,
propsFrom : {}, // applicate a tutte le props di plot
propsTo : {}, // applicate a tutte le props di plot
easing : '' // easing raphael: >, <, <>, backIn, backOut, bounce, elastic
// Opzionale per alcune animazioni, permette di specificare un sotto-tipo
// subType : 0|1|2
},
// Opzioni per le transizioni dei grafici durante un cambiamento di configurazione
/* stepAnimation : {
speed : 600,
delay : 0,
easing : '' // easing raphael: >, <, <>, backIn, backOut, bounce, elastic
},*/
label : {
// Disegna o meno la label interna al grafico
active : false,
// Imposta un offset [X,Y] per la label (le coordinate sono relative al sistema di assi dello specifico settore disegnato.
// Ad es. per il piechart la X <20> la distanza dal centro, la Y lo spostamento ortogonale
//offset : [x, y],
html : false,
// Proprieta' della label (per HTML = false)
props : { fill: 'black', stroke: "none", "font-family": 'Arial', "font-size": "16px" },
// Stile CSS della label (per HTML = true)
style : { cursor : 'default' }
// Posizionamento della label rispetto al punto centrale (+offset) identificato
//frameAnchor : ['start|middle|end', 'top|middle|bottom']
}
/*legend : {
dotType : 'rect',
dotWidth : 10, dotHeight : 10, dotR : 4,
dotProps : { },
textProps : { font: '12px Arial', fill: "#000" }
}*/
},
series : {
// Serie specifica usata quando ci sono "dati vuoti" (ad esempio quando un piechart e' a 0)
empty : {
//plotProps : { fill : "#D0D0D0" },
label : { active : false },
tooltip : { active : false }
}
/*root : {
values : []
}*/
},
features : {
tooltip : {
// Imposta una posizione fissa per tutti i tooltip
//fixedPos : [ x, y]
// Velocita' del fade
fadeDelay : 100,
// Velocita' dello spostamento del tip da un'area all'altra
moveDelay : 300
// E' possibile specificare una funzione che filtra le coordinate del tooltip prima di mostrarlo, permettendo di modificarle
// Nota: le coordinate del mouse sono in mouseAreaData.event.pageX/pageY, e nel caso va ritornato [mouseAreaData.event.pageX, mouseAreaData.event.pageY, true] per indicare che il sistema e' relativo alla pagina)
//positionHandler : function(env, tooltipConf, mouseAreaData, suggestedX, suggestedY) { return [suggestedX, suggestedY] }
},
mousearea : {
// 'single' le aree sensibili sono relative a ogni valore di ogni serie, se 'index' il mouse attiva tutte le serie per un indice
type : 'single',
// In caso di type = 'index', indica se le aree si basano sulle barre ('bar') o sui punti di una linea ('line'). Specificare 'auto' per scegliere automaticamente
indexCenter : 'auto',
// Quanto tempo puo' passare nel passaggio da un'area all'altra per considerarlo uno spostamento di puntatore
areaMoveDelay : 500,
// Se diversi chart specificano lo stesso syncTag quando si attiva l'area di uno si disattivano quelle degli altri
syncTag: false,
// Callback for mouse actions. Parameters passed: (env, serie, index, mouseAreaData)
onMouseEnter : false,
onMouseExit : false,
onMouseChanged : false,
onMouseOver : false,
onMouseOut : false
},
highlight : {
// Evidenzia tutto l'indice con una barra ("bar"), una linea ("line") o una linea centrata sulle barre ("barline"). Se "auto" decide in autonomia tra bar e line
//indexHighlight : 'barline',
indexHighlightProps : { opacity : 1 /*fill : 'yellow', opacity : .3, scale : ".5 1"*/ }
},
animation : {
// Valore di default per la generazione animata degli elementi del grafico (anche per le non-serie: label, grid...)
startAnimation : {
//active : true,
//propsFrom : {}, // applicate a tutte le props di plot
//propsTo : {}, // applicate a tutte le props di plot
speed : 600,
delay : 0,
easing : '' // easing raphael: >, <, <>, backIn, backOut, bounce, elastic
},
// Valore di default per la transizione animata degli elementi del grafico (anche per le non-serie: label, grid...)
stepAnimation : {
speed : 600,
delay : 0,
easing : '' // easing raphael: >, <, <>, backIn, backOut, bounce, elastic
}
},
frameAnimation : {
active : false,
cssFrom : { opacity : 0},
cssTo : { opacity: 1 },
speed : 'slow',
easing : 'linear' // easing jQuery: 'linear' o 'swing'
},
// used to be true
pixelWorkAround : {
active : Raphael.svg
},
label : {},
shadows : {
active : false,
offset : [2, 2], // Per attivare l'ombra, [y, x]
props : {"stroke-width": 0, "stroke-opacity": 0, "fill": "black", "fill-opacity": .3}
},
// BALLOONS: Applicabile solo al funnel (per ora)
balloons : {
active : false,
// Width: se non specificato e' automatico
//width : 200,
// Height: se non specificato e' automatico
//height : 50,
// Lo stile CSS da applicare a ogni balloon
style : { },
// Padding
padding : [ 5, 5 ],
// La distanza dal bordo sinistro
left : 10,
// Percorso della linea: [ [ x, y iniziali (rispetto al punto di inizio standard)], ... [x, y intermedi (rispetto al punto di inizio standard)] ..., [x, y finale (rispetto all'angolo del balloon pi<70> vicino al punto di inizio)] ]
line : [ [ 0, 0 ], [0, 0] ],
// Propriet<65> della linea
lineProps : { }
},
legend : {
horizontal : false,
x : 'auto', // X | auto, (auto solo per horizontal = true)
y : 10,
width : 'auto', // X | auto, (auto solo per horizontal = true)
height : 20,
itemWidth : "fixed", // fixed | auto, solo per horizontal = true
margins : [0, 0, 0, 0],
dotMargins : [10, 5], // sx, dx
borderProps : { fill : "white", stroke : "black", "stroke-width" : 1 },
dotType : 'rect',
dotWidth : 10, dotHeight : 10,
// radius for the dots (used to be 4 but there also was a bug preventing radius support, so moved to 0)
dotR : 0,
dotProps : { type : "rect", width : 10, height : 10 },
textProps : { font: '12px Arial', fill: "#000" }
},
debug : {
active : false
}
},
enableInternalCaching : true,
nop : 0
},
line : {
template : 'common',
// absolute margin left to both sides of each column / column group.
barMargins : 0,
// overlap between additional columns over the previous one (ignored for the first serie)
barOverlapPerc : 0,
// disable this if you want to use null values and want the lines/area to be broken over null values
avgOverNulls: true,
// Axis
defaultAxis : {
// [non per asse x] Normalizza il valore massimo dell'asse in modo che tutte le label abbiamo al massimo N cifre significative
// (Es: se il max e' 135 e normalize = 2 verra' impostato il max a 140, ma se il numero di label in y e' 3 verr<72> impostato 150)
normalize: 2,
// Permette di impostare i valori minimi e massimi di asse (invece di autorilevarli)
min: 0, //max: x,
// Imposta un testo da usare come prefisso e suffisso delle label
//prefix : "", suffix : "",
// Visualizza o meno le label dell'asse
labels: false,
// Distanza tra le label e l'asse relativo
labelsDistance: 8,
// [solo asse x] Rotazione (in gradi) delle label. Se specificato ignora i valori di labelsAnchor e labelsProps['text-anchor']
labelsRotate: 0,
// Proprieta' grafiche delle label
labelsProps : {font: '10px Arial', fill: "#000"},
// Compatta il numero mostrato nella label usando i suffissi specificati per migliaia, milioni...
//labelsCompactUnits : ['k', 'M'],
// Permette di specificare una funzione esterna che si occupa di formattare (o in generale trasformare) la label
//labelsFormatHandler : function (label) { return label },
// Salta le prime N label
//labelsSkip : 0,
// Force alignment for the label. Auto will automatically center it for x axis (also considering labelsRotate), "end" for l axis, "start" for the right axis.
//labelsAnchor : "auto"
// [solo asse x] Force an alternative position for the X axis labels. Auto will automatically choose the right position depending on "labelsCenter", the type of charts (bars vs lines), and labelsRotate.
//labelsPos : "auto",
// Automatically hide labels that would overlap previous labels.
//labelsHideCovered : true,
// Inserisce un margine alla label (a sinistra se in asse x, in alto se in altri assi)
//labelsMargin: 10,
// [solo asse x] If labelsHideCovered = true, make sure each label have at least this space before the next one.
//labelsMarginRight: 0,
// Distanza del titolo dall'asse
titleDistance : 25, titleDistanceIE : .75,
// Proprieta' grafiche del titolo
titleProps : {font: '12px Arial', fill: "#000", "font-weight": "bold"}
},
axis : {
x : { titleDistanceIE : 1.2 }
},
defaultSeries : {
// Tipo di serie, puo' essere 'line' o 'bar'
type : 'line',
// L'asse di riferimento della serie. Gli assi "l" ed "r" sono i 2 assi visibili destro e sinistro.
// E' possibile inserire anche un asse arbitrario (che non sar<61> visibile)
axis : 'l',
// Specificare cumulative = true se i valori inseriti per la serie sono cumulativi
cumulative : false,
// In caso di type="line" indica l'arrotondamento della linea
rounded : 1,
// Mette il punto di intersezione al centro dell'intervallo invece che al limite (per allineamento con bars). Se 'auto' decide autonomamente
lineCenter : 'auto',
// Permette di impilare le serie (i valori di uno iniziano dove finiscono quelli del precedente) con un altra (purche' dello stesso tipo)
// Specificare "true" per impilare con la serie visibile precedente, oppure il nome della serie sulla quale impilare
// stacked : false,
plotProps : {"stroke-width": 1, "stroke-linejoin": "round"},
barWidthPerc: 100,
//DELETED: barProps : {"width-perc" : 100, "stroke-width": 1, "fill-opacity" : .3},
// Attiva o disattiva il riempimento
fill : false,
fillProps : {stroke: "none", "stroke-width" : 0, "stroke-opacity": 0, opacity: .3},
dot : false,
dotProps : {size: 4, stroke: "#000", zindex: 5},
dotShowOnNull : false,
mouseareaShowOnNull : false,
startAnimation : {
plotPropsFrom : false,
// DELETED linePropsFrom : false,
fillPropsFrom : false,
dotPropsFrom : false,
//DELETED barPropsFrom : false,
shadowPropsFrom : false
}
},
features : {
grid : {
// N. di divisioni sull'asse X. Se "auto" si basa sulla label da visualizzare. Se "0" imposta draw[vertical] = false
// Da notare che se "auto" allora la prima e l'ultima linea (bordi) le fa vedere sempre (se ci sono le label). Se invece e' un numero si comporta come ny: fa vedere i bordi solo se forzato con forceBorder
nx : "auto",
// N. di divisione sull'asse Y. Se "0" imposta draw[horizontal] = false
ny : 4,
// Disegna o meno la griglia. Si puo' specificare un array [horizontal, vertical]
draw : false,
// Forza la visualizzazione dei bordi/assi. Se true disegna comunque i bordi (anche se draw = false o se non ci sono label),
// altrimenti si basa sulle regole standard di draw e presenza label (per asse x)
// Puo' essere un booleano singolo o un array di bordi [up, dx, down, sx]
forceBorder : false,
// Proprieta' di visualizzazione griglia
props : {stroke: '#e0e0e0', "stroke-width": 1},
// Dimensioni extra delle rette [up, dx, down, sx]
extra : [0, 0, 0, 0],
// Indica se le label (e le rispettive linee del grid) vanno centrate sulle barre (true), quindi tra 2 linee, o sui punti della serie (false), quindi su una sola linea
// Se specificato "auto" decide in autonomia
labelsCenter : "auto",
// Display a rectangular region with properties specied for every even/odd vertical/horizontal grid division
evenVProps : false,
oddVProps : false,
evenHProps : false,
oddHProps : false,
ticks : {
// Attiva le barrette sugli assi [x, l, r]
active : [false, false, false],
// Dimensioni da prima dell'asse a dopo l'asse
size : [10, 10],
// Proprieta' di visualizzazione griglia
props : {stroke: '#e0e0e0', "stroke-width": 1}
}
}
},
nop : 0
},
pie : {
template : 'common',
// Coordinate del centro, se non specificate vengono autodeterminate
//cx : 0, cy : 0,
// Raggio della torta, se non specificato viene autodeterminato
//r : 0
// Radius in percentage of the available space
//rPerc : 80
// Angolo dal quale iniziare a disegnare le fette, in gradi
startAngle : 0,
// Disegna la torta con le fette in senso orario (invece dell'orientamento standard per gradi, in senso antiorario)
clockwise : false,
// Soglia (rapporto sul totale) entro la quale una fetta non viene visualizzata
valueThresold : 0.006,
// @since elycharts 2.1.5 (previously there was no margins support so when we implemented it we had to add a 0 margin
// here to not start adding the common margin to every pie user
margins : [0, 0, 0, 0],
defaultSeries : {
// r: .5, raggio usato solo per questo spicchio, se <=1 e' in rapporto al raggio generale
// inside: X, inserisce questo spicchio dentro un altro (funziona solo inside: precedente, e non gestisce + spicchi dentro l'altro)
}
},
funnel : {
template : 'common',
rh: 0, // height of ellipsis (for top and bottom cuts)
method: 'width', // width/cutarea
topSector: 0, // height factor of top cylinder
topSectorProps : { fill: "#d0d0d0" },
bottomSector: .1, // height factor of bottom cylinder
bottomSectorProps : { fill: "#d0d0d0" },
edgeProps : { fill: "#c0c0c0", "stroke-width": 1, opacity: 1 },
nop : 0
},
barline : {
template : 'common',
// Imposta il valore massimo per la scala (altrimenti prende il valore + alto)
// max : X
// Impostare direction = rtl per creare un grafico che va da destra a sinistra
direction : 'ltr'
}
}
})(jQuery);
/********* Source File: src/elycharts_core.js*********/
/**********************************************************************
* ELYCHARTS
* A Javascript library to generate interactive charts with vectorial graphics.
*
* Copyright (c) 2010-2014 Void Labs s.n.c. (http://void.it)
* Licensed under the MIT (http://creativecommons.org/licenses/MIT/) license.
**********************************************************************/
(function($) {
if (!$.elycharts)
$.elycharts = {};
$.elycharts.lastId = 0;
/***********************************************************************
* INITIALIZATION / MAIN CALL
**********************************************************************/
$.fn.chart = function($options) {
if (!this.length)
return this;
var $env = this.data('elycharts_env');
if (typeof $options == "string") {
if ($options.toLowerCase() == "config")
return $env ? $env.opt : false;
if ($options.toLowerCase() == "clear") {
if ($env) {
if ($.elycharts.featuresmanager) $.elycharts.featuresmanager.clear($env);
$env.paper.clear();
$env.cache = false;
if ($env.autoresize) $(window).unbind('resize', $env.autoresize);
this.html("");
this.data('elycharts_env', false);
}
}
return this;
}
if (!$env) {
// First call, initialization
if ($options)
$options = _extendAndNormalizeOptions($options);
if (!$options || !$options.type || !$.elycharts.templates[$options.type]) {
alert('ElyCharts ERROR: chart type is not specified');
return false;
}
$env = _initEnv(this, $options);
this.data('elycharts_env', $env);
} else {
if (!$options) $options = {};
$options = _normalizeOptions($options, $env.opt);
// Already initialized
$env.oldopt = common._clone($env.opt);
$env.opt = $.extend(true, $env.opt, $options);
$env.newopt = $options;
$env.oldwidth = $env.width;
$env.oldheight = $env.height;
}
$env.cache = $options['enableInternalCaching'] ? {} : false;
_processGenericConfig($env, $options);
if ($env.opt.autoresize) {
if (!$env.autoresize) {
var that = this;
$env.autoresize = _debounce(function() {
that.chart();
});
$(window).bind('resize', $env.autoresize);
}
} else {
if ($env.autoresize) {
$(window).unbind('resize', $env.autoresize);
$env.autoresize = false;
}
}
var pieces = $.elycharts[$env.opt.type].draw($env);
if ($env.pieces) {
pieces = _updatePieces($env, $env.pieces, pieces);
}
common._show($env, pieces);
$env.pieces = pieces;
return this;
}
function _updatePieces(env, pieces1, pieces2, section, serie, internal) {
// Se pieces2 == null deve essere nascosto tutto pieces1
var newpieces = [], newpiece;
var j = 0;
for (var i = 0; i < pieces1.length; i ++) {
// Se il piece attuale c'e' solo in pieces2 lo riporto nei nuovi, impostando come gia' mostrato
// A meno che internal = true (siamo in un multipath, nel caso se una cosa non c'e' va considerata da togliere)
if (pieces2 && (j >= pieces2.length || !common.samePiecePath(pieces1[i], pieces2[j]))) {
if (!internal) {
pieces1[i].show = false;
newpieces.push(pieces1[i]);
} else {
newpiece = { path : false, attr : false };
newpiece.show = true;
newpiece.animation = {
element : pieces1[i].element ? pieces1[i].element : false,
speed : 0,
easing : '',
delay : 0
}
newpieces.push(newpiece);
}
}
// Bisogna gestire la transizione dal vecchio piece al nuovo
else {
newpiece = pieces2 ? pieces2[j] : { path : false, attr : false };
newpiece.show = true;
if (typeof pieces1[i].paths == 'undefined') {
newpiece.animation = {
element : pieces1[i].element ? pieces1[i].element : false,
speed : 0,
easing : '',
delay : 0
}
} else {
// Multiple path piece
newpiece.paths = _updatePieces(env, pieces1[i].paths, pieces2[j].paths, pieces1[i].section, pieces1[i].serie, true);
}
newpieces.push(newpiece);
j++;
}
}
// If there are pieces left in pieces2 i must add them unchanged
if (pieces2)
for (; j < pieces2.length; j++)
newpieces.push(pieces2[j]);
return newpieces;
};
// http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/
function _debounce(func, threshold, execAsap) {
var timeout;
return function debounced () {
var obj = this, args = arguments;
function delayed () {
if (!execAsap) func.apply(obj, args);
timeout = null;
};
if (timeout) clearTimeout(timeout);
else if (execAsap) func.apply(obj, args);
timeout = setTimeout(delayed, threshold || 100);
};
}
/**
* Must be called only in first call to .chart, to initialize elycharts environment.
*/
function _initEnv($container, $options) {
var $env = {
id : $.elycharts.lastId ++,
paper : common._RaphaelInstance($container.get()[0], 0, 0),
container : $container,
plots : [],
opt : $options
};
// Rendering a transparent pixel up-left. Thay way SVG area is well-covered (else the position starts at first real object, and that mess-ups everything)
$env.paper.rect(0,0,1,1).attr({opacity: 0});
$.elycharts[$options.type].init($env);
return $env;
}
function _processGenericConfig($env, $options) {
if ($options.style)
$env.container.css($options.style);
$env.width = $options.width ? $options.width : $env.container.width();
$env.height = $options.height ? $options.height : $env.container.height();
$env.paper.setSize($env.width, $env.height);
}
/**
* Must be called in first call to .chart, to build the full config structure and normalize it.
*/
function _extendAndNormalizeOptions($options) {
var k;
// Compatibility with old $.elysia_charts.default_options and $.elysia_charts.templates
if ($.elysia_charts) {
if ($.elysia_charts.default_options)
for (k in $.elysia_charts.default_options)
$.elycharts.templates[k] = $.elysia_charts.default_options[k];
if ($.elysia_charts.templates)
for (k in $.elysia_charts.templates)
$.elycharts.templates[k] = $.elysia_charts.templates[k];
}
// TODO Optimize extend cycle
while ($options.template) {
var d = $options.template;
delete $options.template;
$options = $.extend(true, {}, $.elycharts.templates[d], $options);
}
if (!$options.template && $options.type) {
$options.template = $options.type;
while ($options.template) {
d = $options.template;
delete $options.template;
$options = $.extend(true, {}, $.elycharts.templates[d], $options);
}
}
return _normalizeOptions($options, $options);
}
/**
* Normalize options passed (primarly for backward compatibility)
*/
function _normalizeOptions($options, $fullopt) {
if ($options.type == 'pie' || $options.type == 'funnel') {
if ($options.values && $.isArray($options.values) && !$.isArray($options.values[0]))
$options.values = { root : $options.values };
if ($options.tooltips && $.isArray($options.tooltips) && !$.isArray($options.tooltips[0]))
$options.tooltips = { root : $options.tooltips };
if ($options.anchors && $.isArray($options.anchors) && !$.isArray($options.anchors[0]))
$options.anchors = { root : $options.anchors };
if ($options.balloons && $.isArray($options.balloons) && !$.isArray($options.balloons[0]))
$options.balloons = { root : $options.balloons };
if ($options.legend && $.isArray($options.legend) && !$.isArray($options.legend[0]))
$options.legend = { root : $options.legend };
}
if ($options.defaultSeries) {
var plotType = $options.defaultSeries.type ? $options.defaultSeries.type : ($fullopt.defaultSeries.type ? $fullopt.defaultSeries.type : $fullopt.type);
_normalizeOptionsSerie($options.defaultSeries, $fullopt.type, plotType, $fullopt);
}
if ($options.series)
for (var serie in $options.series) {
var seriePlotType = $options.series[serie].type ? $options.series[serie].type : ($fullopt.series[serie].type ? $fullopt.series[serie].type : (plotType ? plotType : $fullopt.type));
_normalizeOptionsSerie($options.series[serie], $fullopt.type, seriePlotType, $fullopt);
}
if ($options.type == 'line') {
if (!$options.features)
$options.features = {};
if (!$options.features.grid)
$options.features.grid = {};
if (typeof $options.gridNX != 'undefined') {
$options.features.grid.nx = $options.gridNX;
delete $options.gridNX;
}
if (typeof $options.gridNY != 'undefined') {
$options.features.grid.ny = $options.gridNY;
delete $options.gridNY;
}
if (typeof $options.gridProps != 'undefined') {
$options.features.grid.props = $options.gridProps;
delete $options.gridProps;
}
if (typeof $options.gridExtra != 'undefined') {
$options.features.grid.extra = $options.gridExtra;
delete $options.gridExtra;
}
if (typeof $options.gridForceBorder != 'undefined') {
$options.features.grid.forceBorder = $options.gridForceBorder;
delete $options.gridForceBorder;
}
if ($options.defaultAxis && $options.defaultAxis.normalize && ($options.defaultAxis.normalize == 'auto' || $options.defaultAxis.normalize == 'autony'))
$options.defaultAxis.normalize = 2;
if ($options.axis)
for (var axis in $options.axis)
if ($options.axis[axis] && $options.axis[axis].normalize && ($options.axis[axis].normalize == 'auto' || $options.axis[axis].normalize == 'autony'))
$options.axis[axis].normalize = 2;
}
return $options;
}
/**
* Manage "color" attribute, the stackedWith legacy and values "color" properties.
* @param $section Section part of external conf passed
* @param $type Chart type
* @param $plotType for line chart can be "line" or "bar", for other types is equal to chart type.
*/
function _normalizeOptionsSerie($section, $type, $plotType, $fullopt) {
if ($section.stackedWith) {
$section.stacked = $section.stackedWith;
delete $section.stackedWith;
}
}
/***********************************************************************
* COMMON
**********************************************************************/
$.elycharts.common = {
_RaphaelInstance : function(c, w, h) {
var r = Raphael(c, w, h);
r.customAttributes.slice = function (cx, cy, r, rint, aa1, aa2) {
// Method body is for clockwise angles, but parameters passed are ccw
a1 = 360 - aa2; a2 = 360 - aa1;
//a1 = aa1; a2 = aa2;
var flag = (a2 - a1) > 180;
a1 = (a1 % 360) * Math.PI / 180;
a2 = (a2 % 360) * Math.PI / 180;
// a1 == a2 (but they where different before) means that there is a complete round (eg: 0-360). This should be shown
if (a1 == a2 && aa1 != aa2)
a2 += 359.99 * Math.PI / 180;
return { path : rint ? [
["M", cx + r * Math.cos(a1), cy + r * Math.sin(a1)],
["A", r, r, 0, +flag, 1, cx + r * Math.cos(a2), cy + r * Math.sin(a2)],
["L", cx + rint * Math.cos(a2), cy + rint * Math.sin(a2)],
//["L", cx + rint * Math.cos(a1), cy + rint * Math.sin(a1)],
["A", rint, rint, 0, +flag, 0, cx + rint * Math.cos(a1), cy + rint * Math.sin(a1)],
["z"]
] : [
["M", cx, cy],
["l", r * Math.cos(a1), r * Math.sin(a1)],
["A", r, r, 0, +flag, 1, cx + r * Math.cos(a2), cy + r * Math.sin(a2)],
["z"]
] };
};
return r;
},
_clone : function(obj){
if(obj == null || typeof(obj) != 'object')
return obj;
if (obj.constructor == Array)
return [].concat(obj);
var temp = new obj.constructor(); // changed (twice)
for(var key in obj)
temp[key] = this._clone(obj[key]);
return temp;
},
compactUnits : function(val, units) {
for (var i = units.length - 1; i >= 0; i--) {
var v = val / Math.pow(1000, i + 1);
//console.warn(i, units[i], v, v * 10 % 10);
if (v >= 1 && v * 10 % 10 == 0)
return v + units[i];
}
return val;
},
getElementOriginalAttrs : function(element) {
var attr = $(element.node).data('original-attr');
if (!attr) {
attr = element.attr();
$(element.node).data('original-attr', attr);
}
return attr;
},
findInPieces : function(pieces, section, serie, index, subsection) {
for (var i = 0; i < pieces.length; i++) {
if (
(typeof section == undefined || section == -1 || section == false || pieces[i].section == section) &&
(typeof serie == undefined || serie == -1 || serie == false || pieces[i].serie == serie) &&
(typeof index == undefined || index == -1 || index == false || pieces[i].index == index) &&
(typeof subsection == undefined || subsection == -1 || subsection == false || pieces[i].subSection == subsection)
)
return pieces[i];
}
return false;
},
samePiecePath : function(piece1, piece2) {
return (((typeof piece1.section == undefined || piece1.section == -1 || piece1.section == false) && (typeof piece2.section == undefined || piece2.section == -1 || piece2.section == false)) || piece1.section == piece2.section) &&
(((typeof piece1.serie == undefined || piece1.serie == -1 || piece1.serie == false) && (typeof piece2.serie == undefined || piece2.serie == -1 || piece2.serie == false)) || piece1.serie == piece2.serie) &&
(((typeof piece1.index == undefined || piece1.index == -1 || piece1.index == false) && (typeof piece2.index == undefined || piece2.index == -1 || piece2.index == false)) || piece1.index == piece2.index) &&
(((typeof piece1.subSection == undefined || piece1.subSection == -1 || piece1.subSection == false) && (typeof piece2.subSection == undefined || piece2.subSection == -1 || piece2.subSection == false)) || piece1.subSection == piece2.subSection);
},
executeIfChanged : function(env, changes) {
if (!env.newopt)
return true;
for (var i = 0; i < changes.length; i++) {
if (changes[i][changes[i].length - 1] == "*") {
for (var j in env.newopt)
if (j.substring(0, changes[i].length - 1) + "*" == changes[i])
return true;
}
else if (changes[i] == 'series' && (env.newopt.series || env.newopt.defaultSeries))
return true;
else if (changes[i] == 'axis' && (env.newopt.axis || env.newopt.defaultAxis))
return true;
else if (changes[i] == 'width' && (env.oldwidth != env.width))
return true;
else if (changes[i] == 'height' && (env.oldheight != env.height))
return true;
else if (changes[i].substring(0, 9) == "features.") {
changes[i] = changes[i].substring(9);
if (env.newopt.features && env.newopt.features[changes[i]])
return true;
}
else if (typeof env.newopt[changes[i]] != 'undefined')
return true;
}
return false;
},
/**
* Can be called for a whole serie or for a given index of the serie.
* returns the color for that item considering valuesPalette, seriesPalette and inheritance
*/
getItemColor : function(env, serie, index) {
var props = this.areaProps(env, 'Series', serie, index);
if (props.color) return props.color;
if (index !== false && props.valuesPalette) return props.valuesPalette[index % props.valuesPalette.length];
if (env.opt.seriesPalette) {
var serieIndex = 0;
for(seriekey in env.opt.values) {
if (serie == seriekey) return env.opt.seriesPalette[serieIndex % env.opt.seriesPalette.length];
else serieIndex++;
}
}
},
/**
* Given an expandKey as array of array it sets the color to the nested tree unless it is already defined.
* So [ [ 'parent', 'child' ], [ 'item' ] ] will try to put color in props.parent.child and props.item unless
* they already exists.
*/
colorize : function(env, props, expandKeys, color) {
if (color) {
for (k in expandKeys) {
var p = props;
var i = 0;
for (i = 0; i < expandKeys[k].length - 1; i++) {
if (!p[expandKeys[k][i]]) p[expandKeys[k][i]] = {};
p = p[expandKeys[k][i]];
}
if (!p[expandKeys[k][expandKeys[k].length-1]]) p[expandKeys[k][expandKeys[k].length-1]] = color;
}
}
},
/**
* Ottiene le proprietà di una "Area" definita nella configurazione (options),
* identificata da section / serie / index / subsection, e facendo il merge
* di tutti i defaults innestati.
*/
areaProps : function(env, section, serie, index, subsection) {
var props;
var sectionProps = env.opt[section.toLowerCase()];
// TODO fare una cache e fix del toLowerCase (devono solo fare la prima lettera
if (!subsection) {
if (typeof serie == 'undefined' || !serie)
props = sectionProps;
else {
var cacheKey = section+'/'+serie+'/'+index;
if (env.cache && env.cache.areaPropsCache && env.cache.areaPropsCache[cacheKey]) {
props = env.cache.areaPropsCache[cacheKey];
}
else {
props = this._clone(env.opt['default' + section]);
if (sectionProps && sectionProps[serie])
props = $.extend(true, props, sectionProps[serie]);
if ((typeof index != 'undefined') && index >= 0 && props['values'] && props['values'][index])
props = $.extend(true, props, props['values'][index]);
if (env.cache) {
if (!env.cache.areaPropsCache) env.cache.areaPropsCache = {};
env.cache.areaPropsCache[cacheKey] = props;
}
}
}
} else {
var subsectionKey = subsection.toLowerCase();
props = this._clone(env.opt[subsectionKey]);
if (typeof serie == 'undefined' || !serie) {
if (sectionProps && sectionProps[subsectionKey])
props = $.extend(true, props, sectionProps[subsectionKey]);
} else {
if (env.opt['default' + section] && env.opt['default' + section][subsectionKey])
props = $.extend(true, props, env.opt['default' + section][subsectionKey]);
if (sectionProps && sectionProps[serie] && sectionProps[serie][subsectionKey])
props = $.extend(true, props, sectionProps[serie][subsectionKey]);
if ((typeof index != 'undefined') && index > 0 && props['values'] && props['values'][index])
props = $.extend(true, props, props['values'][index]);
}
}
return props;
},
_absrectpath : function(x1, y1, x2, y2, r) {
if (r) {
// we can use 'a' or 'Q' for the same result.
var res = [
['M',x1,y1+r], ['a', r, r, 0, 0, 1, r, -r], //['Q',x1,y1, x1+r,y1],
['L',x2-r,y1], ['a', r, r, 0, 0, 1, r, r], //['Q',x2,y1, x2,y1+r],
['L',x2,y2-r], ['a', r, r, 0, 0, 1, -r, r], // ['Q',x2,y2, x2-r,y2],
['L',x1+r,y2], ['a', r, r, 0, 0, 1, -r, -r], // ['Q',x1,y2, x1,y2-r],
['z']
];
return res;
} else return [['M', x1, y1], ['L', x1, y2], ['L', x2, y2], ['L', x2, y1], ['z']];
},
_linepathAnchors : function(p1x, p1y, p2x, p2y, p3x, p3y, rounded) {
var method = 1;
if (rounded && rounded.length) {
method = rounded[1];
rounded = rounded[0];
}
if (!rounded)
rounded = 1;
var l1 = (p2x - p1x) / 2,
l2 = (p3x - p2x) / 2,
a = Math.atan(Math.abs(p2x - p1x) / Math.abs(p2y - p1y)),
b = Math.atan(Math.abs(p3x - p2x) / Math.abs(p2y - p3y));
a = (p1y < p2y && p2x > p1x) || (p1y > p2y && p2x < p1x) ? Math.PI - a : a;
b = (p3y < p2y && p3x > p2x) || (p3y > p2y && p3x < p2x) ? Math.PI - b : b;
if (method == 2) {
// If added by Bago to avoid curves beyond min or max
if ((a - Math.PI / 2) * (b - Math.PI / 2) > 0) {
a = 0;
b = 0;
} else {
if (Math.abs(a - Math.PI / 2) < Math.abs(b - Math.PI / 2))
b = Math.PI - a;
else
a = Math.PI - b;
}
}
var alpha = Math.PI / 2 - ((a + b) % (Math.PI * 2)) / 2,
dx1 = l1 * Math.sin(alpha + a) / 2 / rounded,
dy1 = l1 * Math.cos(alpha + a) / 2 / rounded,
dx2 = l2 * Math.sin(alpha + b) / 2 / rounded,
dy2 = l2 * Math.cos(alpha + b) / 2 / rounded;
return {
x1: p2x - dx1,
y1: p2y + dy1,
x2: p2x + dx2,
y2: p2y + dy2
};
},
_linepath : function ( points, rounded ) {
var path = [];
if (rounded) {
var anc = false;
for (var j = 0, jj = points.length; j < jj ; j++) {
var x = points[j][0], y = points[j][1];
if (x != null && y != null) {
if (anc) {
if (j + 1 != jj && points[j + 1][0] != null && points[j + 1][1] != null) {
var a = this._linepathAnchors(points[j - 1][0], points[j - 1][1], points[j][0], points[j][1], points[j + 1][0], points[j + 1][1], rounded);
path.push([ "C", anc[0], anc[1], a.x1, a.y1, points[j][0], points[j][1] ]);
// path.push([ "M", anc[0], anc[1] ]);
// path.push([ "L", a.x1, a.y1 ]);
// path.push([ "M", points[j][0], points[j][1] ]);
anc = [ a.x2, a.y2 ];
} else {
path.push([ "C", anc[0], anc[1], points[j][0], points[j][1], points[j][0], points[j][1] ]);
anc = [ points[j][0], points[j][1] ];
}
} else {
path.push([ "M", points[j][0], points[j][1] ]);
anc = [ points[j][0], points[j][1] ];
}
} else anc = false;
}
} else {
var prevx = null;
var prevy = null;
for (var i = 0; i < points.length; i++) {
var x = points[i][0], y = points[i][1];
if (x != null && y != null) {
path.push([prevx == null || prevy == null ? "M" : "L", x, y]);
}
prevx = x;
prevy = y;
}
}
return path;
},
_lineareapath : function (points1, points2, rounded) {
var path = this._linepath(points1, rounded);
var path2 = this._linepath(points2.reverse(), rounded);
var finalPath = [];
var firstPushed = null;
for (var i = 0; i <= path.length; i++) {
if (i == path.length || path[i][0] == "M") {
if (firstPushed != null) {
for (var j = path.length - i; j <= path.length - firstPushed; j++) {
if (path2[j][0] == "M") finalPath.push([ "L", path2[j][1], path2[j][2] ]);
else finalPath.push(path2[j]);
}
finalPath.push(['z']);
firstPushed = null;
}
if (i != path.length) finalPath.push(path[i]);
} else {
finalPath.push(path[i]);
if (firstPushed == null) firstPushed = i;
}
}
return finalPath;
},
/**
* Prende la coordinata X di un passo di un path
*/
getX : function(p, pos) {
switch (p[0]) {
case 'CIRCLE':
return p[1];
case 'RECT':
return p[!pos ? 1 : 3];
case 'SLICE':
return p[1];
default:
return p[p.length - 2];
}
},
/**
* Prende la coordinata Y di un passo di un path
*/
getY : function(p, pos) {
switch (p[0]) {
case 'CIRCLE':
return p[2];
case 'RECT':
return p[!pos ? 2 : 4];
case 'SLICE':
return p[2];
default:
return p[p.length - 1];
}
},
/**
* Prende il centro di un path
*
* @param offset un offset [x,y] da applicare. Da notare che gli assi potrebbero essere dipendenti dalla figura
* (ad esempio per lo SLICE x e' l'asse che passa dal centro del cerchio, y l'ortogonale).
*/
getCenter: function(path, offset) {
if (!path.path)
return false;
if (path.path.length == 0)
return false;
if (!offset)
offset = [0, 0];
if (path.center)
return [path.center[0] + offset[0], path.center[1] + offset[1]];
var p = path.path[0];
switch (p[0]) {
case 'CIRCLE':
return [p[1] + offset[0], p[2] + offset[1]];
case 'RECT':
return [(p[1] + p[2])/2 + offset[0], (p[3] + p[4])/2 + offset[1]];
case 'SLICE':
var popangle = p[5] + (p[6] - p[5]) / 2;
var rad = Math.PI / 180;
return [
p[1] + (p[4] + ((p[3] - p[4]) / 2) + offset[0]) * Math.cos(-popangle * rad) + offset[1] * Math.cos((-popangle-90) * rad),
p[2] + (p[4] + ((p[3] - p[4]) / 2) + offset[0]) * Math.sin(-popangle * rad) + offset[1] * Math.sin((-popangle-90) * rad)
];
}
// WARN Complex paths not supported
alert('ElyCharts: getCenter with complex path not supported');
return false;
},
/**
* Sposta il path passato di un offset [x,y]
* Il risultato e' il nuovo path
*
* @param offset un offset [x,y] da applicare. Da notare che gli assi potrebbero essere dipendenti dalla figura
* (ad esempio per lo SLICE x e' l'asse che passa dal centro del cerchio, y l'ortogonale).
* @param marginlimit se true non sposta oltre i margini del grafico (applicabile solo su path standard o RECT)
* @param simple se true lo spostamento e' sempre fatto sul sistema [x, y] complessivo (altrimenti alcuni elementi, come lo SLICE,
* si muovono sul proprio sistema di coordinate - la x muove lungo il raggio e la y lungo l'ortogonale)
*/
movePath : function(env, path, offset, marginlimit, simple) {
var p = [], i;
if (path.length == 1 && path[0][0] == 'RECT')
return [ [path[0][0], this._movePathX(env, path[0][1], offset[0], marginlimit), this._movePathY(env, path[0][2], offset[1], marginlimit), this._movePathX(env, path[0][3], offset[0], marginlimit), this._movePathY(env, path[0][4], offset[1], marginlimit), path[0][5]] ];
if (path.length == 1 && path[0][0] == 'SLICE') {
if (!simple) {
var popangle = path[0][5] + (path[0][6] - path[0][5]) / 2;
var rad = Math.PI / 180;
var x = path[0][1] + offset[0] * Math.cos(- popangle * rad) + offset[1] * Math.cos((-popangle-90) * rad);
var y = path[0][2] + offset[0] * Math.sin(- popangle * rad) + offset[1] * Math.cos((-popangle-90) * rad);
return [ [path[0][0], x, y, path[0][3], path[0][4], path[0][5], path[0][6] ] ];
}
else
return [ [ path[0][0], path[0][1] + offset[0], path[0][2] + offset[1], path[0][3], path[0][4], path[0][5], path[0][6] ] ];
}
if (path.length == 1 && path[0][0] == 'CIRCLE')
return [ [ path[0][0], path[0][1] + offset[0], path[0][2] + offset[1], path[0][3] ] ];
if (path.length == 1 && path[0][0] == 'TEXT')
return [ [ path[0][0], path[0][1], path[0][2] + offset[0], path[0][3] + offset[1] ] ];
if (path.length == 1 && path[0][0] == 'LINE') {
for (i = 0; i < path[0][1].length; i++)
p.push( [ this._movePathX(env, path[0][1][i][0], offset[0], marginlimit), this._movePathY(env, path[0][1][i][1], offset[1], marginlimit) ] );
return [ [ path[0][0], p, path[0][2] ] ];
}
if (path.length == 1 && path[0][0] == 'LINEAREA') {
for (i = 0; i < path[0][1].length; i++)
p.push( [ this._movePathX(env, path[0][1][i][0], offset[0], marginlimit), this._movePathY(env, path[0][1][i][1], offset[1], marginlimit) ] );
var pp = [];
for (i = 0; i < path[0][2].length; i++)
pp.push( [ this._movePathX(env, path[0][2][i][0], offset[0], marginlimit), this._movePathY(env, path[0][2][i][1], offset[1], marginlimit) ] );
return [ [ path[0][0], p, pp, path[0][3] ] ];
}
var newpath = [];
// http://www.w3.org/TR/SVG/paths.html#PathData
for (var j = 0; j < path.length; j++) {
var o = path[j];
switch (o[0]) {
// TODO the translation for lowercase actions are all wrong!
// relative movements do not need to be adjusted for moving (or at most, only the first one have to).
// TODO relative movements this way cannot be forced to stay in marginlimit!
case 'M': case 'm': case 'L': case 'l': case 'T': case 't':
// (x y)+
newpath.push([o[0], this._movePathX(env, o[1], offset[0], marginlimit), this._movePathY(env, o[2], offset[1], marginlimit)]);
break;
case 'A': case 'a':
// (rx ry x-axis-rotation large-arc-flag sweep-flag x y)+
newpath.push([o[0], o[1], o[2], o[3], o[4], o[5], this._movePathX(env, o[6], offset[0], marginlimit), this._movePathY(env, o[7], offset[1], marginlimit)]);
break;
case 'C': case 'c':
// Fixed for uppercase C in 2.1.5
// (x1 y1 x2 y2 x y)+
newpath.push([o[0],
this._movePathX(env, o[1], offset[0], marginlimit), this._movePathY(env, o[2], offset[1], marginlimit),
this._movePathX(env, o[3], offset[0], marginlimit), this._movePathY(env, o[4], offset[1], marginlimit),
this._movePathX(env, o[5], offset[0], marginlimit), this._movePathY(env, o[6], offset[1], marginlimit)
]);
break;
case 'S': case 's': case 'Q': case 'q':
// Fixed for uppercase Q in 2.1.5
// (x1 y1 x y)+
// newpath.push([o[0], o[1], o[2], this._movePathX(env, o[3], offset[0], marginlimit), this._movePathY(env, o[4], offset[1], marginlimit)]);
newpath.push([o[0],
this._movePathX(env, o[1], offset[0], marginlimit), this._movePathY(env, o[2], offset[1], marginlimit),
this._movePathX(env, o[3], offset[0], marginlimit), this._movePathY(env, o[4], offset[1], marginlimit)
]);
break;
case 'z': case 'Z':
newpath.push([o[0]]);
break;
}
}
return newpath;
},
_movePathX : function(env, x, dx, marginlimit) {
if (x == null) return null;
if (!marginlimit)
return x + dx;
x = x + dx;
return dx > 0 && x > env.width - env.opt.margins[1] ? env.width - env.opt.margins[1] : (dx < 0 && x < env.opt.margins[3] ? env.opt.margins[3] : x);
},
_movePathY : function(env, y, dy, marginlimit) {
if (y == null) return null;
if (!marginlimit)
return y + dy;
y = y + dy;
return dy > 0 && y > env.height - env.opt.margins[2] ? env.height - env.opt.margins[2] : (dy < 0 && y < env.opt.margins[0] ? env.opt.margins[0] : y);
},
/**
* Ritorna le proprieta SVG da impostare per visualizzare il path non SVG passato (se applicabile, per CIRCLE e TEXT non lo e')
*/
getSVGProps : function(env, origPath, prevprops) {
var path = this._preparePathShow(env, origPath);
var props = prevprops ? prevprops : {};
var type = 'path', value;
if (path.length == 1 && path[0][0] == 'RECT')
value = common._absrectpath(path[0][1], path[0][2], path[0][3], path[0][4], path[0][5]);
else if (path.length == 1 && path[0][0] == 'SLICE') {
type = 'slice';
value = [ path[0][1], path[0][2], path[0][3], path[0][4], path[0][5], path[0][6] ];
} else if (path.length == 1 && path[0][0] == 'LINE')
value = common._linepath( path[0][1], path[0][2] );
else if (path.length == 1 && path[0][0] == 'LINEAREA')
value = common._lineareapath( path[0][1], path[0][2], path[0][3] );
else if (path.length == 1 && (path[0][0] == 'CIRCLE' || path[0][0] == 'TEXT' || path[0][0] == 'DOMELEMENT' || path[0][0] == 'RELEMENT'))
return prevprops ? prevprops : false;
else
value = path;
if (type != 'path' || (value && value.length > 0))
props[type] = value;
else if (!prevprops)
return false;
return props;
},
/**
* Disegna il path passato
* Gestisce la feature pixelWorkAround
*/
showPath : function(env, path, paper) {
if (!paper)
paper = env.paper;
if (path.length == 1 && path[0][0] == 'CIRCLE') {
path = this._preparePathShow(env, path);
return paper.circle(path[0][1], path[0][2], path[0][3]);
}
if (path.length == 1 && path[0][0] == 'TEXT') {
path = this._preparePathShow(env, path);
return paper.text(path[0][2], path[0][3], path[0][1]);
}
var props = this.getSVGProps(env, path);
// Props must be with some data in it
var hasdata = false;
for (var k in props) {
hasdata = true;
break;
}
return props && hasdata ? paper.path().attr(props) : false;
},
/**
* Applica al path le modifiche per poterlo visualizzare
* Per ora applica solo pixelWorkAround
*/
_preparePathShow : function(env, path) {
return env.opt.features.pixelWorkAround.active ? this.movePath(env, this._clone(path), [.5, .5], false, true) : path;
},
/**
* Ritorna gli attributi Raphael completi di un piece
* Per attributi completi si intende l'insieme di attributi specificato,
* assieme a tutti gli attributi calcolati che determinano lo stato
* iniziale di un piece (e permettono di farlo ritornare a tale stato).
* In genere viene aggiunto il path SVG, per il circle vengono aggiunti
* i dati x,y,r
*/
getPieceFullAttr : function(env, piece) {
if (!piece.fullattr) {
piece.fullattr = this._clone(piece.attr);
if (piece.path)
switch (piece.path[0][0]) {
case 'CIRCLE':
var ppath = this._preparePathShow(env, piece.path);
piece.fullattr.cx = ppath[0][1];
piece.fullattr.cy = ppath[0][2];
piece.fullattr.r = ppath[0][3];
break;
case 'TEXT': case 'DOMELEMENT': case 'RELEMENT':
break;
default:
piece.fullattr = this.getSVGProps(env, piece.path, piece.fullattr);
}
if (typeof piece.fullattr.opacity == 'undefined')
piece.fullattr.opacity = 1;
}
return piece.fullattr;
},
_show : function(env, origPieces) {
if ($.elycharts.featuresmanager) $.elycharts.featuresmanager.beforeShow(env, origPieces);
pieces = this._getSortedPathData(origPieces);
this._animationStackStart(env);
var previousElement = false;
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if ((typeof piece.show == 'undefined' || piece.show) && (typeof piece.parent == 'undefined' || typeof piece.parent.show == 'undefined' || piece.parent.show)) {
// If there is piece.animation.element, this is the old element that must be transformed to the new one
piece.element = piece.animation && piece.animation.element ? piece.animation.element : false;
piece.hide = false;
if (!piece.path) {
// Element should not be shown or must be hidden: nothing to prepare
piece.hide = true;
} else if (piece.path.length == 1 && piece.path[0][0] == 'TEXT') {
// TEXT
// Animation is not supported, so if there's an old element i must hide it (with force = true to hide it for sure, even if there's a new version of same element)
if (piece.element) {
common.animationStackPush(env, piece, piece.element, false, piece.animation.speed, piece.animation.easing, piece.animation.delay, true);
piece.animation.element = false;
}
piece.element = this.showPath(env, piece.path);
// If this is a transition i must position new element
if (piece.element && env.newopt && previousElement)
piece.element.insertAfter(previousElement);
} else if (piece.path.length == 1 && piece.path[0][0] == 'DOMELEMENT') {
// DOMELEMENT
// Already shown
// Animation not supported
} else if (piece.path.length == 1 && piece.path[0][0] == 'RELEMENT') {
// RAPHAEL ELEMENT
// Already shown
// Animation is not supported, so if there's an old element i must hide it (with force = true to hide it for sure, even if there's a new version of same element)
if (piece.element) {
common.animationStackPush(env, piece, piece.element, false, piece.animation.speed, piece.animation.easing, piece.animation.delay, true);
piece.animation.element = false;
}
piece.element = piece.path[0][1];
if (piece.element && previousElement)
piece.element.insertAfter(previousElement);
piece.attr = false;
} else {
// OTHERS
if (!piece.element) {
if (piece.animation && piece.animation.startPath && piece.animation.startPath.length)
piece.element = this.showPath(env, piece.animation.startPath);
else
piece.element = this.showPath(env, piece.path);
// If this is a transition i must position new element
if (piece.element && env.newopt && previousElement)
piece.element.insertAfter(previousElement);
}
}
if (piece.element) {
if (piece.attr) {
if (!piece.animation) {
// Standard piece visualization
if (typeof piece.attr.opacity == 'undefined')
piece.attr.opacity = 1;
piece.element.attr(piece.attr);
} else {
// Piece animation
if (!piece.animation.element)
piece.element.attr(piece.animation.startAttr ? piece.animation.startAttr : piece.attr);
//if (typeof animationAttr.opacity == 'undefined')
// animationAttr.opacity = 1;
common.animationStackPush(env, piece, piece.element, this.getPieceFullAttr(env, piece), piece.animation.speed, piece.animation.easing, piece.animation.delay);
}
} else if (piece.hide)
// Hide the piece
common.animationStackPush(env, piece, piece.element, false, piece.animation.speed, piece.animation.easing, piece.animation.delay);
previousElement = piece.element;
}
}
}
this._animationStackEnd(env);
if ($.elycharts.featuresmanager) $.elycharts.featuresmanager.afterShow(env, origPieces);
},
/**
* Given an array of pieces, return an array of single pathdata contained in pieces, sorted by zindex
*/
_getSortedPathData : function(pieces) {
res = [];
for (var i = 0; i < pieces.length; i++) {
var piece = pieces[i];
if (piece.paths) {
for (var j = 0; j < piece.paths.length; j++) {
piece.paths[j].pos = res.length;
piece.paths[j].parent = piece;
res.push(piece.paths[j]);
}
} else {
piece.pos = res.length;
piece.parent = false;
res.push(piece);
}
}
return res.sort(function (a, b) {
var za = typeof a.attr == 'undefined' || typeof a.attr.zindex == 'undefined' ? ( !a.parent || typeof a.parent.attr == 'undefined' || typeof a.parent.attr.zindex == 'undefined' ? 0 : a.parent.attr.zindex ) : a.attr.zindex;
var zb = typeof b.attr == 'undefined' || typeof b.attr.zindex == 'undefined' ? ( !b.parent || typeof b.parent.attr == 'undefined' || typeof b.parent.attr.zindex == 'undefined' ? 0 : b.parent.attr.zindex ) : b.attr.zindex;
return za < zb ? -1 : (za > zb ? 1 : (a.pos < b.pos ? -1 : (a.pos > b.pos ? 1 : 0)));
});
},
_animationStackStart : function(env) {
if (!env.animationStackDepth || env.animationStackDepth == 0) {
env.animationStackDepth = 0;
env.animationStack = {};
}
env.animationStackDepth ++;
},
_animationStackEnd : function(env) {
env.animationStackDepth --;
if (env.animationStackDepth == 0) {
for (var delay in env.animationStack) {
this._animationStackAnimate(env.animationStack[delay], delay);
delete env.animationStack[delay];
}
env.animationStack = {};
}
},
/**
* Inserisce l'animazione richiesta nello stack di animazioni.
* Nel caso lo stack non sia inizializzato esegue subito l'animazione.
*/
animationStackPush : function(env, piece, element, newattr, speed, easing, delay, force) {
if (typeof delay == 'undefined')
delay = 0;
if (!env.animationStackDepth || env.animationStackDepth == 0) {
this._animationStackAnimate([{piece : piece, object : element, props : newattr, speed: speed, easing : easing, force : force}], delay);
} else {
if (!env.animationStack[delay])
env.animationStack[delay] = [];
env.animationStack[delay].push({piece : piece, object : element, props : newattr, speed: speed, easing : easing, force : force});
}
},
_animationStackAnimate : function(stack, delay) {
var caller = this;
var func = function() {
var a = stack.pop();
var anim = caller._animationStackAnimateElement(a);
while (stack.length > 0) {
var b = stack.pop();
caller._animationStackAnimateElement(b, a, anim);
}
}
if (delay > 0)
setTimeout(func, delay);
else
func();
},
_animationStackAnimateElement : function (a, awith, awithanim) {
//console.warn('call', a.piece.animationInProgress, a.force, a.piece.path, a.piece);
if (a.force || !a.piece.animationInProgress) {
// Metodo non documentato per bloccare l'animazione corrente
a.object.stop();
if (!a.props)
a.props = { opacity : 0 }; // TODO Sarebbe da rimuovere l'elemento alla fine
if (!a.speed || a.speed <= 0) {
//console.warn('direct');
a.object.attr(a.props);
a.piece.animationInProgress = false;
return;
}
a.piece.animationInProgress = true;
//console.warn('START', a.piece.animationInProgress, a.piece.path, a.piece);
// NOTA onEnd non viene chiamato se l'animazione viene bloccata con stop
var onEnd = function() {
//console.warn('END', a.piece.animationInProgress, a.piece);
a.piece.animationInProgress = false
}
if (Raphael.animation) {
var anim = Raphael.animation(a.props, a.speed, a.easing ? a.easing : 'linear', onEnd);
if (awith) {
// console.warn('animateWith', awith, awithanim, anim);
a.object.animateWith(awith, awithanim, anim);
} else {
// console.warn('animate', anim);
a.object.animate(anim);
}
return anim;
} else {
if (awith) {
// console.warn('animateWith', awith, awithanim, anim);
a.object.animateWith(awith, a.props, a.speed, a.easing ? a.easing : 'linear', onEnd);
} else {
// console.warn('animate', anim);
a.object.animate(a.props, a.speed, a.easing ? a.easing : 'linear', onEnd);
}
return null;
}
}
//else console.warn('SKIP', a.piece.animationInProgress, a.piece.path, a.piece);
return null;
}
}
var common = $.elycharts.common;
/***********************************************************************
* FEATURESMANAGER
**********************************************************************/
$.elycharts.featuresmanager = {
managers : [],
initialized : false,
register : function(manager, priority) {
$.elycharts.featuresmanager.managers.push([priority, manager]);
$.elycharts.featuresmanager.initialized = false;
},
init : function() {
$.elycharts.featuresmanager.managers.sort(function(a, b) { return a[0] < b[0] ? -1 : (a[0] == b[0] ? 0 : 1) });
$.elycharts.featuresmanager.initialized = true;
},
clear : function(env) {
if (!$.elycharts.featuresmanager.initialized)
this.init();
// reverse cycle over manager
for (var i = $.elycharts.featuresmanager.managers.length - 1; i >= 0; i--)
if ($.elycharts.featuresmanager.managers[i][1].clear)
$.elycharts.featuresmanager.managers[i][1].clear(env);
},
beforeShow : function(env, pieces) {
if (!$.elycharts.featuresmanager.initialized)
this.init();
for (var i = 0; i < $.elycharts.featuresmanager.managers.length; i++)
if ($.elycharts.featuresmanager.managers[i][1].beforeShow)
$.elycharts.featuresmanager.managers[i][1].beforeShow(env, pieces);
},
afterShow : function(env, pieces) {
if (!$.elycharts.featuresmanager.initialized)
this.init();
for (var i = 0; i < $.elycharts.featuresmanager.managers.length; i++)
if ($.elycharts.featuresmanager.managers[i][1].afterShow)
$.elycharts.featuresmanager.managers[i][1].afterShow(env, pieces);
},
onMouseOver : function(env, serie, index, mouseAreaData) {
if (!$.elycharts.featuresmanager.initialized)
this.init();
for (var i = 0; i < $.elycharts.featuresmanager.managers.length; i++)
if ($.elycharts.featuresmanager.managers[i][1].onMouseOver)
$.elycharts.featuresmanager.managers[i][1].onMouseOver(env, serie, index, mouseAreaData);
},
onMouseOut : function(env, serie, index, mouseAreaData) {
if (!$.elycharts.featuresmanager.initialized)
this.init();
for (var i = 0; i < $.elycharts.featuresmanager.managers.length; i++)
if ($.elycharts.featuresmanager.managers[i][1].onMouseOut)
$.elycharts.featuresmanager.managers[i][1].onMouseOut(env, serie, index, mouseAreaData);
},
onMouseEnter : function(env, serie, index, mouseAreaData) {
if (!$.elycharts.featuresmanager.initialized)
this.init();
for (var i = 0; i < $.elycharts.featuresmanager.managers.length; i++)
if ($.elycharts.featuresmanager.managers[i][1].onMouseEnter)
$.elycharts.featuresmanager.managers[i][1].onMouseEnter(env, serie, index, mouseAreaData);
},
onMouseChanged : function(env, serie, index, mouseAreaData) {
if (!$.elycharts.featuresmanager.initialized)
this.init();
for (var i = 0; i < $.elycharts.featuresmanager.managers.length; i++)
if ($.elycharts.featuresmanager.managers[i][1].onMouseChanged)
$.elycharts.featuresmanager.managers[i][1].onMouseChanged(env, serie, index, mouseAreaData);
},
onMouseExit : function(env, serie, index, mouseAreaData) {
if (!$.elycharts.featuresmanager.initialized)
this.init();
for (var i = 0; i < $.elycharts.featuresmanager.managers.length; i++)
if ($.elycharts.featuresmanager.managers[i][1].onMouseExit)
$.elycharts.featuresmanager.managers[i][1].onMouseExit(env, serie, index, mouseAreaData);
}
}
})(jQuery);
/***********************************************
* OGGETTI USATI:
PIECE:
Contiene un elemento da visualizzare nel grafico. E' un oggetto con queste proprietà:
- section,[serie],[index],[subsection]: Dati che permettono di identificare che tipo
di elemento è e a quale blocco della configurazione appartiene.
Ad esempio gli elementi principali del chart hanno
section="Series", serie=nome della serie, subSection = 'Plot'
- [paths]: Contiene un array di pathdata, nel caso questo piece è costituito da
piu' sottoelementi (ad esempio i Dots, o gli elementi di un Pie o Funnel)
- [PATHDATA.*]: Se questo piece e' costituito da un solo elemento, i suoi dati sono
memorizzati direttamente nella root di PIECE.
- show: Proprieta' usata internamente per decidere se questo piece dovrà essere
visualizzato o meno (in genere nel caso di una transizione che non ha variato
questo piece, che quindi puo' essere lasciato allo stato precedente)
- hide: Proprieta' usata internamente per decidere se l'elemento va nascosto,
usato in caso di transizione se l'elemento non è piu' presente.
PATHDATA:
I dati utili per visualizzare un path nel canvas:
- PATH: Il path che permette di disegnare l'elemento. Se NULL l'elemento è vuoto/ da
non visualizzare (instanziato solo come placeholder)
- attr: gli attributi Raphael dell'elemento. NULL se path è NULL.
- [center]: centro del path
- [rect]: rettangolo che include il path
PATH:
Un array in cui ogni elemento determina un passo del percorso per disegnare il grafico.
E' una astrazione sul PATH SVG effettivo, e puo' avere alcuni valori speciali:
[ [ 'TEXT', testo, x, y ] ]
[ [ 'CIRCLE', x, y, raggio ] ]
[ [ 'RECT', x1, y1, x2, y2, rounded ] ] (x1,y1 dovrebbero essere sempre le coordinate in alto a sx)
[ [ 'SLICE', x, y, raggio, raggio int, angolo1, angolo2 ] ] (gli angoli sono in gradi)
[ [ 'RELEMENT', element ] ] (elemento Raphael gia' disegnato)
[ [ 'DOMELEMENT', element ] ] (elemento DOM - in genere un DIV html - già disegnato)
[ ... Path SVG ... ]
------------------------------------------------------------------------
Z-INDEX:
0 : base
10 : tooltip
20 : interactive area (tutti gli elementi innescati dalla interactive area dovrebbero essere < 20)
25 : label / balloons (potrebbero essere resi cliccabili dall'esterno, quindi > 20)
------------------------------------------------------------------------
USEFUL RESOURCES:
http://docs.jquery.com/Plugins/Authoring
http://www.learningjquery.com/2007/10/a-plugin-development-pattern
http://dean.edwards.name/packer/2/usage/#special-chars
http://raphaeljs.com/reference.html#attr
TODO
* ottimizzare common.areaProps
* rifare la posizione del tooltip del pie
* ripristinare shadow
*********************************************/
/********* Source File: src/elycharts_manager_anchor.js*********/
/**********************************************************************
* ELYCHARTS
* A Javascript library to generate interactive charts with vectorial graphics.
*
* Copyright (c) 2010-2014 Void Labs s.n.c. (http://void.it)
* Licensed under the MIT (http://creativecommons.org/licenses/MIT/) license.
**********************************************************************/
(function($) {
var common = $.elycharts.common;
/***********************************************************************
* FEATURE: ANCHOR
*
* Permette di collegare i dati del grafico con delle aree esterne,
* identificate dal loro selettore CSS, e di interagire con esse.
**********************************************************************/
$.elycharts.anchormanager = {
afterShow : function(env, pieces) {
// Prendo le aree gestite da mouseAreas, e metto i miei listener
// Non c'e' bisogno di gestire il clean per una chiamata successiva, lo fa gia' il mouseareamanager
// Tranne per i bind degli eventi jquery
if (!env.opt.anchors)
return;
if (!env.anchorBinds)
env.anchorBinds = [];
while (env.anchorBinds.length) {
var b = env.anchorBinds.pop();
$(b[0]).unbind(b[1], b[2]);
}
for (var i = 0; i < env.mouseAreas.length; i++) {
var serie = env.mouseAreas[i].piece ? env.mouseAreas[i].piece.serie : false;
var anc;
if (serie)
anc = env.opt.anchors[serie][env.mouseAreas[i].index];
else
anc = env.opt.anchors[env.mouseAreas[i].index];
if (anc && env.mouseAreas[i].props.anchor && env.mouseAreas[i].props.anchor.highlight) {
(function(env, mouseAreaData, anc, caller) {
var f1 = function() { caller.anchorMouseOver(env, mouseAreaData); };
var f2 = function() { caller.anchorMouseOut(env, mouseAreaData); };
if (!env.mouseAreas[i].props.anchor.useMouseEnter) {
env.anchorBinds.push([anc, 'mouseover', f1]);
env.anchorBinds.push([anc, 'mouseout', f2]);
$(anc).mouseover(f1);
$(anc).mouseout(f2);
} else {
env.anchorBinds.push([anc, 'mouseenter', f1]);
env.anchorBinds.push([anc, 'mouseleave', f2]);
$(anc).mouseenter(f1);
$(anc).mouseleave(f2);
}
})(env, env.mouseAreas[i], anc, this);
}
}
env.onAnchors = [];
},
anchorMouseOver : function(env, mouseAreaData) {
$.elycharts.highlightmanager.onMouseOver(env, mouseAreaData.piece ? mouseAreaData.piece.serie : false, mouseAreaData.index, mouseAreaData);
},
anchorMouseOut : function(env, mouseAreaData) {
$.elycharts.highlightmanager.onMouseOut(env, mouseAreaData.piece ? mouseAreaData.piece.serie : false, mouseAreaData.index, mouseAreaData);
},
onMouseOver : function(env, serie, index, mouseAreaData) {
if (!env.opt.anchors)
return;
if (mouseAreaData.props.anchor && mouseAreaData.props.anchor.addClass) {
//var serie = mouseAreaData.piece ? mouseAreaData.piece.serie : false;
var anc;
if (serie)
anc = env.opt.anchors[serie][mouseAreaData.index];
else
anc = env.opt.anchors[mouseAreaData.index];
if (anc) {
$(anc).addClass(mouseAreaData.props.anchor.addClass);
env.onAnchors.push([anc, mouseAreaData.props.anchor.addClass]);
}
}
},
onMouseOut : function(env, serie, index, mouseAreaData) {
if (!env.opt.anchors)
return;
while (env.onAnchors.length > 0) {
var o = env.onAnchors.pop();
$(o[0]).removeClass(o[1]);
}
}
}
$.elycharts.featuresmanager.register($.elycharts.anchormanager, 30);
})(jQuery);
/********* Source File: src/elycharts_manager_animation.js*********/
/**********************************************************************
* ELYCHARTS
* A Javascript library to generate interactive charts with vectorial graphics.
*
* Copyright (c) 2010-2014 Void Labs s.n.c. (http://void.it)
* Licensed under the MIT (http://creativecommons.org/licenses/MIT/) license.
**********************************************************************/
(function($) {
var common = $.elycharts.common;
/***********************************************************************
* ANIMATIONMANAGER
**********************************************************************/
$.elycharts.animationmanager = {
beforeShow : function(env, pieces) {
if (!env.newopt)
this.startAnimation(env, pieces);
else
this.stepAnimation(env, pieces);
},
stepAnimation : function(env, pieces) {
pieces = this._stepAnimationInt(env, pieces);
},
_stepAnimationInt : function(env, pieces, section, serie, internal) {
for (var i = 0; i < pieces.length; i++) {
var animationProps = common.areaProps(env, section ? section : pieces[i].section, serie ? serie : pieces[i].serie);
if (animationProps && animationProps.stepAnimation)
animationProps = animationProps.stepAnimation;
else
animationProps = env.opt.features.animation.stepAnimation;
if (typeof pieces[i].paths == 'undefined') {
if (animationProps && animationProps.active && pieces[i].animation) {
pieces[i].animation.speed = animationProps && animationProps.speed ? animationProps.speed : 300;
pieces[i].animation.easing = animationProps && animationProps.easing ? animationProps.easing : '';
pieces[i].animation.delay = animationProps && animationProps.delay ? animationProps.delay : 0;
if (!pieces[i].animation.element)
pieces[i].animation.startAttr = {opacity : 0};
}
} else {
this._stepAnimationInt(env, pieces[i].paths, pieces[i].section, pieces[i].serie, true);
}
}
},
startAnimation : function(env, pieces) {
for (var i = 0; i < pieces.length; i++)
if (pieces[i].paths || pieces[i].path) {
var props = common.areaProps(env, pieces[i].section, pieces[i].serie);
if (props && props.startAnimation)
props = props.startAnimation;
else
props = env.opt.features.animation.startAnimation;
if (props && props.active) {
if (props.type == 'simple' || pieces[i].section != 'Series')
this.animationSimple(env, props, pieces[i]);
if (props.type == 'grow')
this.animationGrow(env, props, pieces[i]);
if (props.type == 'avg')
this.animationAvg(env, props, pieces[i]);
if (props.type == 'reg')
this.animationReg(env, props, pieces[i]);
}
}
},
/**
* Inserisce i dati base di animazione del piece e la transizione di attributi
*/
_animationPiece : function(piece, animationProps, subSection) {
if (piece.paths) {
for (var i = 0; i < piece.paths.length; i++)
this._animationPiece(piece.paths[i], animationProps, subSection);
} else if (piece.path) {
piece.animation = {
speed : animationProps.speed,
easing : animationProps.easing,
delay : animationProps.delay,
startPath : [],
startAttr : common._clone(piece.attr)
};
if (animationProps.propsTo)
piece.attr = $.extend(true, piece.attr, animationProps.propsTo);
if (animationProps.propsFrom)
piece.animation.startAttr = $.extend(true, piece.animation.startAttr, animationProps.propsFrom);
if (subSection && animationProps[subSection.toLowerCase() + 'PropsFrom'])
piece.animation.startAttr = $.extend(true, piece.animation.startAttr, animationProps[subSection.toLowerCase() + 'PropsFrom']);
if (typeof piece.animation.startAttr.opacity != 'undefined' && typeof piece.attr.opacity == 'undefined')
piece.attr.opacity = 1;
}
},
animationSimple : function(env, props, piece) {
this._animationPiece(piece, props, piece.subSection);
},
animationGrow : function(env, props, piece) {
this._animationPiece(piece, props, piece.subSection);
var i, npath, y;
switch (env.opt.type) {
case 'line':
y = env.height - env.opt.margins[2];
switch (piece.subSection) {
case 'Plot':
if (!piece.paths) {
npath = [ 'LINE', [], piece.path[0][2]];
for (i = 0; i < piece.path[0][1].length; i++)
npath[1].push([ piece.path[0][1][i][0], piece.path[0][1][i][1] == null ? null : y ]);
piece.animation.startPath.push(npath);
} else {
for (i = 0; i < piece.paths.length; i++)
if (piece.paths[i].path)
piece.paths[i].animation.startPath.push([ 'RECT', piece.paths[i].path[0][1], y, piece.paths[i].path[0][3], y ]);
}
break;
case 'Fill':
npath = [ 'LINEAREA', [], [], piece.path[0][3]];
for (i = 0; i < piece.path[0][1].length; i++) {
npath[1].push([ piece.path[0][1][i][0], piece.path[0][1][i][1] == null ? null : y ]);
npath[2].push([ piece.path[0][2][i][0], piece.path[0][2][i][1] == null ? null : y ]);
}
piece.animation.startPath.push(npath);
break;
case 'Dot':
for (i = 0; i < piece.paths.length; i++)
if (piece.paths[i].path)
piece.paths[i].animation.startPath.push(['CIRCLE', piece.paths[i].path[0][1], y, piece.paths[i].path[0][3]]);
break;
}
break;
case 'pie':
if (piece.subSection == 'Plot')
for (i = 0; i < piece.paths.length; i++)
if (piece.paths[i].path && piece.paths[i].path[0][0] == 'SLICE')
piece.paths[i].animation.startPath.push([ 'SLICE', piece.paths[i].path[0][1], piece.paths[i].path[0][2], piece.paths[i].path[0][4] + piece.paths[i].path[0][3] * 0.1, piece.paths[i].path[0][4], piece.paths[i].path[0][5], piece.paths[i].path[0][6] ]);
break;
case 'funnel':
alert('Unsupported animation GROW for funnel');
break;
case 'barline':
var x;
if (piece.section == 'Series' && piece.subSection == 'Plot') {
if (!props.subType)
x = env.opt.direction != 'rtl' ? env.opt.margins[3] : env.width - env.opt.margins[1];
else if (props.subType == 1)
x = env.opt.direction != 'rtl' ? env.width - env.opt.margins[1] : env.opt.margins[3];
for (i = 0; i < piece.paths.length; i++)
if (piece.paths[i].path) {
if (!props.subType || props.subType == 1)
piece.paths[i].animation.startPath.push([ 'RECT', x, piece.paths[i].path[0][2], x, piece.paths[i].path[0][4], piece.paths[i].path[0][5] ]);
else {
y = (piece.paths[i].path[0][2] + piece.paths[i].path[0][4]) / 2;
piece.paths[i].animation.startPath.push([ 'RECT', piece.paths[i].path[0][1], y, piece.paths[i].path[0][3], y, piece.paths[i].path[0][5] ]);
}
}
}
break;
}
},
_animationAvgXYArray : function(arr) {
var res = [], avg = 0, i;
var count = 0;
for (i = 0; i < arr.length; i++) if (arr[i][1] != null) {
avg += arr[i][1];
count++;
}
avg = avg / count;
for (i = 0; i < arr.length; i++)
res.push([ arr[i][0], arr[i][1] == null ? null : avg ]);
return res;
},
animationAvg : function(env, props, piece) {
this._animationPiece(piece, props, piece.subSection);
var avg = 0, i, l;
switch (env.opt.type) {
case 'line':
switch (piece.subSection) {
case 'Plot':
if (!piece.paths) {
// LINE
piece.animation.startPath.push([ 'LINE', this._animationAvgXYArray(piece.path[0][1]), piece.path[0][2] ]);
} else {
// BAR
l = 0;
for (i = 0; i < piece.paths.length; i++)
if (piece.paths[i].path) {
l ++;
avg += piece.paths[i].path[0][2];
}
avg = avg / l;
for (i = 0; i < piece.paths.length; i++)
if (piece.paths[i].path)
piece.paths[i].animation.startPath.push([ "RECT", piece.paths[i].path[0][1], avg, piece.paths[i].path[0][3], piece.paths[i].path[0][4] ]);
}
break;
case 'Fill':
piece.animation.startPath.push([ 'LINEAREA', this._animationAvgXYArray(piece.path[0][1]), this._animationAvgXYArray(piece.path[0][2]), piece.path[0][3] ]);
break;
case 'Dot':
l = 0;
for (i = 0; i < piece.paths.length; i++)
if (piece.paths[i].path) {
l ++;
avg += piece.paths[i].path[0][2];
}
avg = avg / l;
for (i = 0; i < piece.paths.length; i++)
if (piece.paths[i].path)
piece.paths[i].animation.startPath.push(['CIRCLE', piece.paths[i].path[0][1], avg, piece.paths[i].path[0][3]]);
break;
}
break;
case 'pie':
var delta = 360 / piece.paths.length;
if (piece.subSection == 'Plot')
for (i = 0; i < piece.paths.length; i++)
if (piece.paths[i].path && piece.paths[i].path[0][0] == 'SLICE')
piece.paths[i].animation.startPath.push([ 'SLICE', piece.paths[i].path[0][1], piece.paths[i].path[0][2], piece.paths[i].path[0][3], piece.paths[i].path[0][4], i * delta, (i + 1) * delta ]);
break;
case 'funnel':
alert('Unsupported animation AVG for funnel');
break;
case 'barline':
alert('Unsupported animation AVG for barline');
break;
}
},
_animationRegXYArray : function(arr) {
var res = [];
var c = arr.length;
var start = 0;
var end = c - 1;
while (arr[start][1] == null) start++;
while (arr[end][1] == null) end--;
var y1 = arr[0][1];
var y2 = arr[c - 1][1];
for (var i = 0; i < arr.length; i++) {
if (arr[i][1] == null) res.push([ arr[i][0], null ]);
else {
res.push([ arr[i][0], arr[start][1] + (arr[end][1] - arr[start][1]) / (end - start) * ( i - start) ]);
}
}
return res;
},
animationReg : function(env, props, piece) {
this._animationPiece(piece, props, piece.subSection);
var i, c, y1, y2;
switch (env.opt.type) {
case 'line':
switch (piece.subSection) {
case 'Plot':
if (!piece.paths) {
// LINE
piece.animation.startPath.push([ 'LINE', this._animationRegXYArray(piece.path[0][1]), piece.path[0][2] ]);
} else {
// BAR
c = piece.paths.length;
if (c > 1) {
for (i = 0; !piece.paths[i].path && i < piece.paths.length; i++) {}
y1 = piece.paths[i].path ? common.getY(piece.paths[i].path[0]) : 0;
for (i = piece.paths.length - 1; !piece.paths[i].path && i >= 0; i--) {}
y2 = piece.paths[i].path ? common.getY(piece.paths[i].path[0]) : 0;
for (i = 0; i < piece.paths.length; i++)
if (piece.paths[i].path)
piece.paths[i].animation.startPath.push([ "RECT", piece.paths[i].path[0][1], y1 + (y2 - y1) / (c - 1) * i, piece.paths[i].path[0][3], piece.paths[i].path[0][4] ]);
}
}
break;
case 'Fill':
piece.animation.startPath.push([ 'LINEAREA', this._animationRegXYArray(piece.path[0][1]), this._animationRegXYArray(piece.path[0][2]), piece.path[0][3] ]);
break;
case 'Dot':
c = piece.paths.length;
if (c > 1) {
for (i = 0; !piece.paths[i].path && i < piece.paths.length; i++) {}
y1 = piece.paths[i].path ? common.getY(piece.paths[i].path[0]) : 0;
for (i = piece.paths.length - 1; !piece.paths[i].path && i >= 0; i--) {}
y2 = piece.paths[i].path ? common.getY(piece.paths[i].path[0]) : 0;
for (i = 0; i < piece.paths.length; i++)
if (piece.paths[i].path)
piece.paths[i].animation.startPath.push(['CIRCLE', piece.paths[i].path[0][1], y1 + (y2 - y1) / (c - 1) * i, piece.paths[i].path[0][3]]);
}
break;
}
break;
case 'pie':
alert('Unsupported animation REG for pie');
break;
case 'funnel':
alert('Unsupported animation REG for funnel');
break;
case 'barline':
alert('Unsupported animation REG for barline');
break;
}
}
}
$.elycharts.featuresmanager.register($.elycharts.animationmanager, 10);
/***********************************************************************
* FRAMEANIMATIONMANAGER
**********************************************************************/
$.elycharts.frameanimationmanager = {
beforeShow : function(env, pieces) {
if (env.opt.features.frameAnimation.active)
$(env.container.get(0)).css(env.opt.features.frameAnimation.cssFrom);
},
afterShow : function(env, pieces) {
if (env.opt.features.frameAnimation.active)
env.container.animate(env.opt.features.frameAnimation.cssTo, env.opt.features.frameAnimation.speed, env.opt.features.frameAnimation.easing);
}
};
$.elycharts.featuresmanager.register($.elycharts.frameanimationmanager, 90);
})(jQuery);
/********* Source File: src/elycharts_manager_highlight.js*********/
/**********************************************************************
* ELYCHARTS
* A Javascript library to generate interactive charts with vectorial graphics.
*
* Copyright (c) 2010-2014 Void Labs s.n.c. (http://void.it)
* Licensed under the MIT (http://creativecommons.org/licenses/MIT/) license.
**********************************************************************/
(function($) {
var common = $.elycharts.common;
/***********************************************************************
* FEATURE: HIGHLIGHT
*
* Permette di evidenziare in vari modi l'area in cui si passa con il
* mouse.
**********************************************************************/
$.elycharts.highlightmanager = {
removeHighlighted : function(env, full) {
if (env.highlighted)
while (env.highlighted.length > 0) {
var o = env.highlighted.pop();
if (o.piece) {
if (full)
common.animationStackPush(env, o.piece, o.piece.element, common.getPieceFullAttr(env, o.piece), o.cfg.restoreSpeed, o.cfg.restoreEasing, 0, true);
} else
o.element.remove();
}
},
afterShow : function(env, pieces) {
if (env.highlighted && env.highlighted.length > 0)
this.removeHighlighted(env, false);
env.highlighted = [];
},
onMouseOver : function(env, serie, index, mouseAreaData) {
var path, element;
// TODO Se non e' attivo l'overlay (per la serie o per tutto) e' inutile fare il resto
// Cerco i piece da evidenziare (tutti quelli che sono costituiti da path multipli)
for (var i = 0; i < mouseAreaData.pieces.length; i++)
// Il loop sotto estrae solo i pieces con array di path (quindi non i line o i fill del linechart ... ma il resto si)
if (mouseAreaData.pieces[i].section == 'Series' && mouseAreaData.pieces[i].paths
&& (!serie || mouseAreaData.pieces[i].serie == serie)
&& mouseAreaData.pieces[i].paths[index] && mouseAreaData.pieces[i].paths[index].element) {
var piece = mouseAreaData.pieces[i].paths[index];
element = piece.element;
path = piece.path;
var attr = common.getElementOriginalAttrs(element);
var newattr = false; // In caso la geometria dell'oggetto è modificata mediante attr (es: per circle) qui memorizza i nuovi attributi
var props = serie ? mouseAreaData.props : common.areaProps(env, mouseAreaData.pieces[i].section, mouseAreaData.pieces[i].serie);
var pelement, ppiece, ppath;
if (path && props.highlight) {
if (props.highlight.scale) {
var scale = props.highlight.scale;
if (typeof scale == 'number')
scale = [scale, scale];
if (path[0][0] == 'RECT') {
var w = path[0][3] - path[0][1];
var h = path[0][4] - path[0][2];
path = [ [ 'RECT', path[0][1], path[0][2] - h * (scale[1] - 1), path[0][3] + w * (scale[0] - 1), path[0][4] ] ];
common.animationStackPush(env, piece, element, common.getSVGProps(env, path), props.highlight.scaleSpeed, props.highlight.scaleEasing);
}
else if (path[0][0] == 'CIRCLE') {
// I pass directly new radius
newattr = {r : path[0][3] * scale[0]};
common.animationStackPush(env, piece, element, newattr, props.highlight.scaleSpeed, props.highlight.scaleEasing);
}
else if (path[0][0] == 'SLICE') {
// Per lo slice x e' il raggio, y e' l'angolo
var d = (path[0][6] - path[0][5]) * (scale[1] - 1) / 2;
if (d > 90)
d = 90;
path = [ [ 'SLICE', path[0][1], path[0][2], path[0][3] * scale[0], path[0][4], path[0][5] - d, path[0][6] + d ] ];
common.animationStackPush(env, piece, element, common.getSVGProps(env, path), props.highlight.scaleSpeed, props.highlight.scaleEasing);
} else if (env.opt.type == 'funnel') {
var dx = (piece.rect[2] - piece.rect[0]) * (scale[0] - 1) / 2;
var dy = (piece.rect[3] - piece.rect[1]) * (scale[1] - 1) / 2;
// Specifico di un settore del funnel
// SHOULD ALREADY BE DONE BY core common.animationStackStart(env);
path = [ common.movePath(env, [ path[0]], [-dx, -dy])[0],
common.movePath(env, [ path[1]], [+dx, -dy])[0],
common.movePath(env, [ path[2]], [+dx, +dy])[0],
common.movePath(env, [ path[3]], [-dx, +dy])[0],
path[4] ];
common.animationStackPush(env, piece, element, common.getSVGProps(env, path), props.highlight.scaleSpeed, props.highlight.scaleEasing, 0, true);
// Se c'e' un piece precedente lo usa, altrimenti cerca un topSector per la riduzione
pelement = false;
if (index > 0) {
ppiece = mouseAreaData.pieces[i].paths[index - 1];
pelement = ppiece.element;
ppath = ppiece.path;
} else {
ppiece = common.findInPieces(mouseAreaData.pieces, 'Sector', 'top');
if (ppiece) {
pelement = ppiece.element;
ppath = ppiece.path;
}
}
if (pelement) {
//pattr = common.getElementOriginalAttrs(pelement);
ppath = [
ppath[0], ppath[1],
common.movePath(env, [ ppath[2]], [+dx, -dy])[0],
common.movePath(env, [ ppath[3]], [-dx, -dy])[0],
ppath[4] ];
common.animationStackPush(env, ppiece, pelement, common.getSVGProps(env, ppath), props.highlight.scaleSpeed, props.highlight.scaleEasing, 0, true);
env.highlighted.push({piece : ppiece, cfg : props.highlight});
}
// Se c'e' un piece successivo lo usa, altrimenti cerca un bottomSector per la riduzione
pelement = false;
if (index < mouseAreaData.pieces[i].paths.length - 1) {
ppiece = mouseAreaData.pieces[i].paths[index + 1];
pelement = ppiece.element;
ppath = ppiece.path;
} else {
ppiece = common.findInPieces(mouseAreaData.pieces, 'Sector', 'bottom');
if (ppiece) {
pelement = ppiece.element;
ppath = ppiece.path;
}
}
if (pelement) {
//var pattr = common.getElementOriginalAttrs(pelement);
ppath = [
common.movePath(env, [ ppath[0]], [-dx, +dy])[0],
common.movePath(env, [ ppath[1]], [+dx, +dy])[0],
ppath[2], ppath[3],
ppath[4] ];
common.animationStackPush(env, ppiece, pelement, common.getSVGProps(env, ppath), props.highlight.scaleSpeed, props.highlight.scaleEasing, 0, true);
env.highlighted.push({piece : ppiece, cfg : props.highlight});
}
// SHOULD ALREADY BE DONE BY core: common.animationStackEnd(env);
}
/* Con scale non va bene
if (!attr.scale)
attr.scale = [1, 1];
element.attr({scale : [scale[0], scale[1]]}); */
}
if (props.highlight.newProps) {
for (var a in props.highlight.newProps)
if (typeof attr[a] == 'undefined')
attr[a] = false;
common.animationStackPush(env, piece, element, props.highlight.newProps);
}
if (props.highlight.move) {
var offset = $.isArray(props.highlight.move) ? props.highlight.move : [props.highlight.move, 0];
path = common.movePath(env, path, offset);
common.animationStackPush(env, piece, element, common.getSVGProps(env, path), props.highlight.moveSpeed, props.highlight.moveEasing);
}
//env.highlighted.push({element : element, attr : attr});
env.highlighted.push({piece : piece, cfg : props.highlight});
if (props.highlight.overlayProps) {
// NOTA: path e' il path modificato dai precedenti (cosi' l'overlay tiene conto della cosa), deve guardare anche a newattr
//BIND: mouseAreaData.listenerDisabled = true;
element = common.showPath(env, path);
if (newattr)
element.attr(newattr);
element.attr(props.highlight.overlayProps);
//BIND: $(element.node).unbind().mouseover(mouseAreaData.mouseover).mouseout(mouseAreaData.mouseout);
// Se metto immediatamente il mouseAreaData.listenerDisabled poi va comunque un mouseout dalla vecchia area e va
// in loop. TODO Rivedere e sistemare anche per tooltip
//BIND: setTimeout(function() { mouseAreaData.listenerDisabled = false; }, 10);
attr = false;
env.highlighted.push({element : element, attr : attr, cfg : props.highlight});
}
}
}
if (env.opt.features.highlight.indexHighlight && env.opt.type == 'line') {
var t = env.opt.features.highlight.indexHighlight;
if (t == 'auto')
t = (env.indexCenter == 'bar' ? 'bar' : 'line');
var delta1 = (env.width - env.opt.margins[3] - env.opt.margins[1]) / (env.opt.labels.length > 0 ? env.opt.labels.length : 1);
var delta2 = (env.width - env.opt.margins[3] - env.opt.margins[1]) / (env.opt.labels.length > 1 ? env.opt.labels.length - 1 : 1);
var lineCenter = true;
switch (t) {
case 'bar':
path = [ ['RECT', env.opt.margins[3] + index * delta1, env.opt.margins[0] ,
env.opt.margins[3] + (index + 1) * delta1, env.height - env.opt.margins[2] ] ];
break;
case 'line':
lineCenter = false;
case 'barline':
var x = Math.round((lineCenter ? delta1 / 2 : 0) + env.opt.margins[3] + index * (lineCenter ? delta1 : delta2));
path = [[ 'M', x, env.opt.margins[0]], ['L', x, env.height - env.opt.margins[2]]];
}
if (path) {
//BIND: mouseAreaData.listenerDisabled = true;
element = common.showPath(env, path).attr(env.opt.features.highlight.indexHighlightProps);
//BIND: $(element.node).unbind().mouseover(mouseAreaData.mouseover).mouseout(mouseAreaData.mouseout);
//BIND: setTimeout(function() { mouseAreaData.listenerDisabled = false; }, 10);
env.highlighted.push({element : element, attr : false, cfg : env.opt.features.highlight});
}
}
},
onMouseOut : function(env, serie, index, mouseAreaData) {
this.removeHighlighted(env, true);
}
};
$.elycharts.featuresmanager.register($.elycharts.highlightmanager, 21);
})(jQuery);
/********* Source File: src/elycharts_manager_label.js*********/
/**********************************************************************
* ELYCHARTS
* A Javascript library to generate interactive charts with vectorial graphics.
*
* Copyright (c) 2010-2014 Void Labs s.n.c. (http://void.it)
* Licensed under the MIT (http://creativecommons.org/licenses/MIT/) license.
**********************************************************************/
(function($) {
var common = $.elycharts.common;
/***********************************************************************
* FEATURE: LABELS
*
* Permette di visualizzare in vari modi le label del grafico.
* In particolare per pie e funnel permette la visualizzazione all'interno
* delle fette.
* Per i line chart le label sono visualizzate già nella gestione assi.
*
* TODO:
* - Comunque per i line chart si potrebbe gestire la visualizzazione
* all'interno delle barre, o sopra i punti.
**********************************************************************/
$.elycharts.labelmanager = {
beforeShow : function(env, pieces) {
if (!common.executeIfChanged(env, ['labels', 'values', 'series']))
return;
if (env.opt.labels && (env.opt.type == 'pie' || env.opt.type == 'funnel')) {
var /*lastSerie = false, */lastIndex = false;
var paths;
for (var i = 0; i < pieces.length; i++) {
if (pieces[i].section == 'Series' && pieces[i].subSection == 'Plot') {
var props = common.areaProps(env, 'Series', pieces[i].serie);
if (env.emptySeries && env.opt.series.empty)
props.label = $.extend(true, props.label, env.opt.series.empty.label);
if (props && props.label && props.label.active) {
paths = [];
for (var index = 0; index < pieces[i].paths.length; index++)
if (pieces[i].paths[index].path) {
//lastSerie = pieces[i].serie;
lastIndex = index;
paths.push(this.showLabel(env, pieces[i], pieces[i].paths[index], pieces[i].serie, index, pieces));
} else
paths.push({ path : false, attr : false });
pieces.push({ section : pieces[i].section, serie : pieces[i].serie, subSection : 'Label', paths: paths });
}
}
else if (pieces[i].section == 'Sector' && pieces[i].serie == 'bottom' && !pieces[i].subSection && lastIndex < env.opt.labels.length - 1) {
paths = [];
paths.push(this.showLabel(env, pieces[i], pieces[i], 'Series', env.opt.labels.length - 1, pieces));
pieces.push({ section : pieces[i].section, serie : pieces[i].serie, subSection : 'Label', paths: paths });
}
}
}
},
showLabel : function(env, piece, path, serie, index, pieces) {
var pp = common.areaProps(env, 'Series', serie, index);
if (env.opt.labels[index] || pp.label.label) {
var p = path;
var label = pp.label.label ? pp.label.label : env.opt.labels[index];
var center = common.getCenter(p, pp.label.offset);
if (!pp.label.html) {
var attr = pp.label.props;
if (pp.label.frameAnchor) {
attr = common._clone(pp.label.props);
attr['text-anchor'] = pp.label.frameAnchor[0];
attr['alignment-baseline'] = pp.label.frameAnchor[1];
}
/*pieces.push({
path : [ [ 'TEXT', label, center[0], center[1] ] ], attr : attr,
section: 'Series', serie : serie, index : index, subSection : 'Label'
});*/
return { path : [ [ 'TEXT', label, center[0], center[1] ] ], attr : attr };
} else {
var opacity = 1;
var style = common._clone(pp.label.style);
var set_opacity = (typeof style.opacity != 'undefined')
if (set_opacity) {
opacity = style.opacity;
style.opacity = 0;
}
style.position = 'absolute';
style['z-index'] = 25;
var el;
if (typeof label == 'string')
el = $('<div>' + label + '</div>').css(style).prependTo(env.container);
else
el = $(label).css(style).prependTo(env.container);
// Centramento corretto label
if (env.opt.features.debug.active && el.height() == 0)
alert('DEBUG: Al gestore label e\' stata passata una label ancora senza dimensioni, quindi ancora non disegnata. Per questo motivo il posizionamento potrebbe non essere correto.');
var posX = center[0];
var posY = center[1];
if (!pp.label.frameAnchor || pp.label.frameAnchor[0] == 'middle')
posX -= el.width() / 2;
else if (pp.label.frameAnchor && pp.label.frameAnchor[0] == 'end')
posX -= el.width();
if (!pp.label.frameAnchor || pp.label.frameAnchor[1] == 'middle')
posY -= el.height() / 2;
else if (pp.label.frameAnchor && pp.label.frameAnchor[1] == 'top')
posY -= el.height();
if (set_opacity)
el.css({ margin: posY + 'px 0 0 ' + posX + 'px', opacity : opacity});
else
el.css({ margin: posY + 'px 0 0 ' + posX + 'px'});
/*pieces.push({
path : [ [ 'DOMELEMENT', el ] ], attr : false,
section: 'Series', serie : serie, index : index, subSection : 'Label'
});*/
return { path : [ [ 'DOMELEMENT', el ] ], attr : false };
}
}
return false;
}
}
$.elycharts.featuresmanager.register($.elycharts.labelmanager, 5);
})(jQuery);
/********* Source File: src/elycharts_manager_legend.js*********/
/**********************************************************************
* ELYCHARTS
* A Javascript library to generate interactive charts with vectorial graphics.
*
* Copyright (c) 2010-2014 Void Labs s.n.c. (http://void.it)
* Licensed under the MIT (http://creativecommons.org/licenses/MIT/) license.
**********************************************************************/
(function($) {
var common = $.elycharts.common;
/***********************************************************************
* FEATURE: LEGEND
**********************************************************************/
$.elycharts.legendmanager = {
afterShow : function(env, pieces) {
// TODO the whole thing should simply return "pieces" whose visibility is handled by core, so to enable animations and
// make things more generic.
if (env.legenditems) {
for (item in env.legenditems) {
env.legenditems[item].remove();
}
env.legenditems = false;
}
if (!env.opt.legend || env.opt.legend.length == 0)
return;
var props = env.opt.features.legend;
if (props === false) return;
var propsx = props.x;
if (propsx == 'auto') {
var autox = 1;
propsx = 0;
}
var propswidth = props.width;
if (propswidth == 'auto') {
var autowidth = 1;
propswidth = env.width;
}
var wauto = 0;
var items = [];
// env.opt.legend normalmente è { serie : 'Legend', ... }, per i pie invece { serie : ['Legend', ...], ... }
var legendCount = 0;
var serie, data, h, w, x, y, xd;
for (serie in env.opt.legend) {
if (env.opt.type != 'pie')
legendCount ++;
else
legendCount += env.opt.legend[serie].length;
}
var i = 0;
for (serie in env.opt.legend) {
if (env.opt.type != 'pie')
data = [ env.opt.legend[serie] ];
else
data = env.opt.legend[serie];
for (var j = 0; j < data.length; j++) {
var sprops = common.areaProps(env, 'Series', serie, env.opt.type == 'pie' ? j : false);
var computedProps = $.extend(true, {}, props);
if (sprops.legend)
computedProps = $.extend(true, computedProps, sprops.legend);
var color = common.getItemColor(env, serie, env.opt.type == 'pie' ? j : false);
if (color) {
common.colorize(env, computedProps, [['dotProps', 'fill']], color);
}
// legacy support for legend dot color inherited from pie "fill"
// TODO maybe we should simply remove this and leave the "color" support only
if (!computedProps.dotProps.fill && env.opt.type == 'pie') {
if (sprops.plotProps && sprops.plotProps.fill)
computedProps.dotProps.fill = sprops.plotProps.fill;
}
var hMargin = props.margins ? props.margins[0] + props.margins[2] : 0;
var wMargin = props.margins ? props.margins[1] + props.margins[3] : 0;
var tMargin = props.margins ? props.margins[0] : 0;
var lMargin = props.margins ? props.margins[3] : 0;
if (!props.horizontal) {
// Posizione dell'angolo in alto a sinistra
h = (props.height - hMargin) / legendCount;
w = propswidth - wMargin;
x = Math.floor(propsx + lMargin);
y = Math.floor(props.y + tMargin + h * i);
} else {
h = props.height - hMargin;
if (!props.itemWidth || props.itemWidth == 'fixed') {
w = (propswidth - wMargin) / legendCount;
x = Math.floor(propsx + lMargin + w * i);
} else {
w = (propswidth - wMargin) - wauto;
x = propsx + lMargin + wauto;
}
y = Math.floor(props.y + tMargin);
}
if (computedProps.dotType == "rect") {
items.push(common.showPath(env, [ [ 'RECT', props.dotMargins[0] + x, y + Math.floor((h - computedProps.dotHeight) / 2), props.dotMargins[0] + x + computedProps.dotWidth, y + Math.floor((h - computedProps.dotHeight) / 2) + computedProps.dotHeight, computedProps.dotR ] ]).attr(computedProps.dotProps));
xd = props.dotMargins[0] + computedProps.dotWidth + props.dotMargins[1];
} else if (computedProps.dotType == "circle") {
items.push(common.showPath(env, [ [ 'CIRCLE', props.dotMargins[0] + x + computedProps.dotR, y + (h / 2), computedProps.dotR ] ]).attr(computedProps.dotProps));
xd = props.dotMargins[0] + computedProps.dotR * 2 + props.dotMargins[1];
}
var text = data[j];
var t = common.showPath(env, [ [ 'TEXT', text, x + xd, y + Math.ceil(h / 2) + (Raphael.VML ? 2 : 0) ] ]).attr({"text-anchor" : "start"}).attr(computedProps.textProps); //.hide();
items.push(t);
while (t.getBBox().width > (w - xd) && t.getBBox().width > 10) {
text = text.substring(0, text.length - 1);
t.attr({text : text});
}
t.show();
if (props.horizontal && props.itemWidth == 'auto')
wauto += xd + t.getBBox().width + 4;
else if (!props.horizontal && autowidth)
wauto = t.getBBox().width + xd > wauto ? t.getBBox().width + xd : wauto;
else
wauto += w;
i++;
}
}
if (autowidth)
propswidth = wauto + props.margins[3] + props.margins[1] - 1;
if (autox) {
propsx = Math.floor((env.width - propswidth) / 2);
for (i in items) {
if (items[i].attrs.x)
items[i].attr('x', items[i].attrs.x + propsx);
else
items[i].attr('path', common.movePath(env, items[i].attrs.path, [propsx, 0]));
}
}
var borderPath = [ [ 'RECT', propsx, props.y, propsx + propswidth, props.y + props.height, props.r ] ];
var border = common.showPath(env, borderPath).attr(props.borderProps);
// The legend rectangle is written as the last one because it depends on the sizes of the contents but it should
// be drawn behind the others, so at the end we bring to front all items but the border
for(i in items) items[i].toFront();
items.unshift(border);
env.legenditems = items;
}
}
$.elycharts.featuresmanager.register($.elycharts.legendmanager, 90);
})(jQuery);
/********* Source File: src/elycharts_manager_mouse.js*********/
/**********************************************************************
* ELYCHARTS
* A Javascript library to generate interactive charts with vectorial graphics.
*
* Copyright (c) 2010-2014 Void Labs s.n.c. (http://void.it)
* Licensed under the MIT (http://creativecommons.org/licenses/MIT/) license.
**********************************************************************/
(function($) {
var featuresmanager = $.elycharts.featuresmanager;
var common = $.elycharts.common;
/***********************************************************************
* MOUSEMANAGER
**********************************************************************/
$.elycharts.mousemanager = {
clear : function(env) {
if (env.mouseLayer) {
env.mouseLayer.remove();
env.mouseLayer = null;
env.mousePaper.clear();
env.mousePaper.remove();
env.mousePaper = null;
env.mouseTimer = null;
env.mouseAreas = null;
// NOTE: do we also need to unbind mouseover/mouseout from areas or is this handled automatically by Raphael?
}
},
afterShow : function(env, pieces) {
if (!env.opt.interactive)
return;
this.clear(env);
env.mouseLayer = $('<div></div>').css({position : 'absolute', 'z-index' : 20, opacity : 1}).prependTo(env.container);
env.mousePaper = common._RaphaelInstance(env.mouseLayer.get(0), env.width, env.height);
var paper = env.mousePaper;
if (env.opt.features.debug.active && typeof DP_Debug != 'undefined') {
env.paper.text(env.width, env.height - 5, 'DEBUG').attr({ 'text-anchor' : 'end', stroke: 'red', opacity: .1 });
paper.text(env.width, env.height - 5, 'DEBUG').attr({ 'text-anchor' : 'end', stroke: 'red', opacity: .1 }).click(function() {
DP_Debug.dump(env.opt, '', false, 4);
});
}
var i, j;
// Adding mouseover only in right area, based on pieces
env.mouseAreas = [];
if (env.opt.features.mousearea.type == 'single') {
// SINGLE: Every serie's index is an area
for (i = 0; i < pieces.length; i++) {
if (pieces[i].mousearea) {
// pathstep
if (!pieces[i].paths) {
// path standard, generating an area for each point
if (pieces[i].path.length >= 1 && (pieces[i].path[0][0] == 'LINE' || pieces[i].path[0][0] == 'LINEAREA'))
for (j = 0; j < pieces[i].path[0][1].length; j++) {
var props = common.areaProps(env, pieces[i].section, pieces[i].serie);
if (props.mouseareaShowOnNull || pieces[i].section != 'Series' || env.opt.values[pieces[i].serie][j] != null)
env.mouseAreas.push({
path : [ [ 'CIRCLE', pieces[i].path[0][1][j][0], pieces[i].path[0][1][j][1], 10 ] ],
piece : pieces[i],
pieces : pieces,
index : j,
props : props
});
}
else // Code below is only for standard path - it should be useless now (now there are only LINE and LINEAREA)
// TODO DELETE
for (j = 0; j < pieces[i].path.length; j++) {
env.mouseAreas.push({
path : [ [ 'CIRCLE', common.getX(pieces[i].path[j]), common.getY(pieces[i].path[j]), 10 ] ],
piece : pieces[i],
pieces : pieces,
index : j,
props : common.areaProps(env, pieces[i].section, pieces[i].serie)
});
}
// paths
} else if (pieces[i].paths) {
// Set of paths (bar graph?), generating overlapped areas
for (j = 0; j < pieces[i].paths.length; j++)
if (pieces[i].paths[j].path)
env.mouseAreas.push({
path : pieces[i].paths[j].path,
piece : pieces[i],
pieces : pieces,
index : j,
props : common.areaProps(env, pieces[i].section, pieces[i].serie)
});
}
}
}
} else {
// INDEX: Each index (in every serie) is an area
var indexCenter = env.opt.features.mousearea.indexCenter;
if (indexCenter == 'auto')
indexCenter = env.indexCenter;
var start, delta;
if (indexCenter == 'bar') {
delta = (env.width - env.opt.margins[3] - env.opt.margins[1]) / (env.opt.labels.length > 0 ? env.opt.labels.length : 1);
start = env.opt.margins[3];
} else {
delta = (env.width - env.opt.margins[3] - env.opt.margins[1]) / (env.opt.labels.length > 1 ? env.opt.labels.length - 1 : 1);
start = env.opt.margins[3] - delta / 2;
}
for (var idx in env.opt.labels) {
// idx can be a string and concatenation results in bad sums.
var index = parseInt(idx);
env.mouseAreas.push({
path : [ [ 'RECT', start + index * delta, env.height - env.opt.margins[2], start + (index + 1) * delta, env.opt.margins[0] ] ],
piece : false,
pieces : pieces,
index : parseInt(index),
props : env.opt.defaultSeries // TODO common.areaProps(env, 'Plot')
});
}
}
var syncenv = false;
if (!env.opt.features.mousearea.syncTag) {
env.mouseareaenv = { chartEnv : false, mouseObj : false, caller : false, inArea : -1, timer : false };
syncenv = env.mouseareaenv;
} else {
if (!$.elycharts.mouseareaenv)
$.elycharts.mouseareaenv = {};
if (!$.elycharts.mouseareaenv[env.opt.features.mousearea.syncTag])
$.elycharts.mouseareaenv[env.opt.features.mousearea.syncTag] = { chartEnv : false, mouseObj : false, caller : false, inArea : -1, timer : false };
syncenv = $.elycharts.mouseareaenv[env.opt.features.mousearea.syncTag];
}
for (i = 0; i < env.mouseAreas.length; i++) {
env.mouseAreas[i].area = common.showPath(env, env.mouseAreas[i].path, paper).attr({stroke: "#000", fill: "#fff", opacity: 0});
(function(env, obj, objidx, caller, syncenv) {
var piece = obj.piece;
var index = obj.index;
obj.mouseover = function(e) {
//BIND: if (obj.listenerDisabled) return;
obj.event = e;
clearTimeout(syncenv.timer);
caller.onMouseOverArea(env, piece, index, obj);
if (syncenv.chartEnv && syncenv.chartEnv.id != env.id) {
// Chart changed, removing old one
syncenv.caller.onMouseExitArea(syncenv.chartEnv, syncenv.mouseObj.piece, syncenv.mouseObj.index, syncenv.mouseObj);
caller.onMouseEnterArea(env, piece, index, obj);
}
else if (syncenv.inArea != objidx) {
if (syncenv.inArea < 0)
caller.onMouseEnterArea(env, piece, index, obj);
else
caller.onMouseChangedArea(env, piece, index, obj);
}
syncenv.chartEnv = env;
syncenv.mouseObj = obj;
syncenv.caller = caller;
syncenv.inArea = objidx;
};
obj.mouseout = function(e) {
//BIND: if (obj.listenerDisabled) return;
obj.event = e;
clearTimeout(syncenv.timer);
caller.onMouseOutArea(env, piece, index, obj);
syncenv.timer = setTimeout(function() {
syncenv.timer = false;
caller.onMouseExitArea(env, piece, index, obj);
syncenv.chartEnv = false;
syncenv.inArea = -1;
}, env.opt.features.mousearea.areaMoveDelay);
};
$(obj.area.node).mouseover(obj.mouseover);
$(obj.area.node).mouseout(obj.mouseout);
})(env, env.mouseAreas[i], i, this, syncenv);
}
},
// Called when mouse enter an area
onMouseOverArea : function(env, piece, index, mouseAreaData) {
//console.warn('over', piece.serie, index);
if (env.opt.features.mousearea.onMouseOver)
env.opt.features.mousearea.onMouseOver(env, mouseAreaData.piece ? mouseAreaData.piece.serie : false, mouseAreaData.index, mouseAreaData);
featuresmanager.onMouseOver(env, mouseAreaData.piece ? mouseAreaData.piece.serie : false, mouseAreaData.index, mouseAreaData);
},
// Called when mouse exit from an area
onMouseOutArea : function(env, piece, index, mouseAreaData) {
//console.warn('out', piece.serie, index);
if (env.opt.features.mousearea.onMouseOut)
env.opt.features.mousearea.onMouseOut(env, mouseAreaData.piece ? mouseAreaData.piece.serie : false, mouseAreaData.index, mouseAreaData);
featuresmanager.onMouseOut(env, mouseAreaData.piece ? mouseAreaData.piece.serie : false, mouseAreaData.index, mouseAreaData);
},
// Called when mouse enter an area from empty space (= it was in no area before)
onMouseEnterArea : function(env, piece, index, mouseAreaData) {
//console.warn('enter', piece.serie, index);
if (env.opt.features.mousearea.onMouseEnter)
env.opt.features.mousearea.onMouseEnter(env, mouseAreaData.piece ? mouseAreaData.piece.serie : false, mouseAreaData.index, mouseAreaData);
featuresmanager.onMouseEnter(env, mouseAreaData.piece ? mouseAreaData.piece.serie : false, mouseAreaData.index, mouseAreaData);
},
// Called when mouse enter an area and it was on another area
onMouseChangedArea : function(env, piece, index, mouseAreaData) {
//console.warn('changed', piece.serie, index);
if (env.opt.features.mousearea.onMouseChanged)
env.opt.features.mousearea.onMouseChanged(env, mouseAreaData.piece ? mouseAreaData.piece.serie : false, mouseAreaData.index, mouseAreaData);
featuresmanager.onMouseChanged(env, mouseAreaData.piece ? mouseAreaData.piece.serie : false, mouseAreaData.index, mouseAreaData);
},
// Called when mouse leaves an area and does not enter in another one (timeout check)
onMouseExitArea : function(env, piece, index, mouseAreaData) {
//console.warn('exit', piece.serie, index);
if (env.opt.features.mousearea.onMouseExit)
env.opt.features.mousearea.onMouseExit(env, mouseAreaData.piece ? mouseAreaData.piece.serie : false, mouseAreaData.index, mouseAreaData);
featuresmanager.onMouseExit(env, mouseAreaData.piece ? mouseAreaData.piece.serie : false, mouseAreaData.index, mouseAreaData);
}
}
$.elycharts.featuresmanager.register($.elycharts.mousemanager, 0);
})(jQuery);
/********* Source File: src/elycharts_manager_tooltip.js*********/
/**********************************************************************
* ELYCHARTS
* A Javascript library to generate interactive charts with vectorial graphics.
*
* Copyright (c) 2010-2014 Void Labs s.n.c. (http://void.it)
* Licensed under the MIT (http://creativecommons.org/licenses/MIT/) license.
**********************************************************************/
(function($) {
var common = $.elycharts.common;
/***********************************************************************
* FEATURE: TOOLTIP
**********************************************************************/
$.elycharts.tooltipmanager = {
clear : function(env) {
if (env.tooltipContainer) {
env.tooltipFrame.clear();
env.tooltipFrame.remove();
env.tooltipFrame = null;
env.tooltipFrameElement = null;
env.tooltipContent.remove();
env.tooltipContent = null;
env.tooltipContainer.remove();
env.tooltipContainer = null;
}
},
afterShow : function(env, pieces) {
this.clear(env);
if (!$.elycharts.tooltipid)
$.elycharts.tooltipid = 0;
$.elycharts.tooltipid ++;
// Preparo il tooltip
env.tooltipContainer = $('<div id="elycharts_tooltip_' + $.elycharts.tooltipid + '" style="position: absolute; top: 100; left: 100; z-index: 10; overflow: hidden; white-space: nowrap; display: none"><div id="elycharts_tooltip_' + $.elycharts.tooltipid + '_frame" style="position: absolute; top: 0; left: 0; z-index: -1"></div><div id="elycharts_tooltip_' + $.elycharts.tooltipid + '_content" style="cursor: default"></div></div>').appendTo(document.body);
env.tooltipFrame = common._RaphaelInstance('elycharts_tooltip_' + $.elycharts.tooltipid + '_frame', 500, 500);
env.tooltipContent = $('#elycharts_tooltip_' + $.elycharts.tooltipid + '_content');
},
_prepareShow : function(env, props, mouseAreaData, tip) {
// Il dimensionamento del tooltip e la view del frame SVG, lo fa solo se width ed height sono specificati
if (props.width && props.width != 'auto' && props.height && props.height != 'auto') {
var delta = props.frameProps && props.frameProps['stroke-width'] ? props.frameProps['stroke-width'] : 0;
env.tooltipContainer.width(props.width + delta + 1).height(props.height + delta + 1);
if (!env.tooltipFrameElement && props.frameProps) {
var framePath = [ [ 'RECT', delta / 2, delta / 2, props.width, props.height, props.roundedCorners ] ];
env.tooltipFrameElement = common.showPath(env, framePath, env.tooltipFrame).attr(props.frameProps);
// env.tooltipFrameElement = env.tooltipFrame.rect(delta / 2, delta / 2, props.width, props.height, props.roundedCorners);
}
}
if (env.tooltipFrameElement) {
env.tooltipFrameElement.attr(props.frameProps);
}
if (props.padding)
env.tooltipContent.css({ padding : props.padding[0] + 'px ' + props.padding[1] + 'px' });
env.tooltipContent.css(props.contentStyle);
env.tooltipContent.html(tip);
//BIND: env.tooltipContainer.unbind().mouseover(mouseAreaData.mouseover).mouseout(mouseAreaData.mouseout);
// WARN: Prendendo env.paper.canvas non va bene...
//var offset = $(env.paper.canvas).offset();
var offset = $(env.container).offset();
if (env.opt.features.tooltip.fixedPos) {
offset.top += env.opt.features.tooltip.fixedPos[1];
offset.left += env.opt.features.tooltip.fixedPos[0];
} else {
var coord = this.getXY(env, props, mouseAreaData);
if (!coord[2]) {
offset.left += coord[0];
while (offset.top + coord[1] < 0)
coord[1] += 20;
offset.top += coord[1];
} else {
offset.left = coord[0];
offset.top = coord[1];
}
}
return { top : offset.top, left : offset.left };
},
/**
* Ritorna [x, y] oppure [x, y, true] se le coordinate sono relative alla pagina (e non al grafico)
*/
getXY : function(env, props, mouseAreaData) {
// NOTA Posizione mouse: mouseAreaData.event.pageX/pageY
var x = 0, y = 0;
if (mouseAreaData.path[0][0] == 'RECT') {
// L'area e' su un rettangolo (un bar o un indice completo), il tooltip lo faccio subito sopra
// Nota: per capire se e' sull'indice completo basta guardare mouseAreaData.piece == null
x = common.getX(mouseAreaData.path[0]) - props.offset[1];
y = common.getY(mouseAreaData.path[0]) - props.height - props.offset[0];
}
else if (mouseAreaData.path[0][0] == 'CIRCLE') {
// L'area e' su un cerchio (punto di un line)
x = common.getX(mouseAreaData.path[0]) - props.offset[1];
y = common.getY(mouseAreaData.path[0]) - props.height - props.offset[0];
}
else if (mouseAreaData.path[0][0] == 'SLICE') {
// L'area è su una fetta di torta (pie)
var path = mouseAreaData.path[0];
// Genera la posizione del tip considerando che deve stare all'interno di un cerchio che è sempre dalla parte opposta dell'area
// e deve essere il piu' vicino possibile all'area
var w = props.width && props.width != 'auto' ? props.width : 100;
var h = props.height && props.height != 'auto' ? props.height : 100;
// Raggio del cerchio che contiene il tip
var cr = Math.sqrt(Math.pow(w,2) + Math.pow(h,2)) / 2;
if (cr > env.opt.r)
cr = env.opt.r;
var tipangle = path[5] + (path[6] - path[5]) / 2 + 180;
var rad = Math.PI / 180;
x = path[1] + cr * Math.cos(- tipangle * rad) - w / 2;
y = path[2] + cr * Math.sin(- tipangle * rad) - h / 2;
}
else if (mouseAreaData.piece && mouseAreaData.piece.paths && mouseAreaData.index >= 0 && mouseAreaData.piece.paths[mouseAreaData.index] && mouseAreaData.piece.paths[mouseAreaData.index].rect) {
// L'area ha una forma complessa, ma abbiamo il rettangolo di contenimento (funnel)
var rect = mouseAreaData.piece.paths[mouseAreaData.index].rect;
x = rect[0] - props.offset[1];
y = rect[1] - props.height - props.offset[0];
}
if (env.opt.features.tooltip.positionHandler)
return env.opt.features.tooltip.positionHandler(env, props, mouseAreaData, x, y);
else
return [x, y];
},
getTip : function(env, serie, index) {
var tip = false;
if (env.opt.tooltips) {
if (typeof env.opt.tooltips == 'function')
tip = env.opt.tooltips(env, serie, index, serie && env.opt.values[serie] && env.opt.values[serie][index] ? env.opt.values[serie][index] : false, env.opt.labels && env.opt.labels[index] ? env.opt.labels[index] : false);
else {
if (serie && env.opt.tooltips[serie] && env.opt.tooltips[serie][index])
tip = env.opt.tooltips[serie][index];
else if (!serie && env.opt.tooltips[index])
tip = env.opt.tooltips[index];
}
}
return tip;
},
_getProps : function(env, serie, index, mouseAreaData) {
var props = mouseAreaData.props.tooltip;
if (env.emptySeries && env.opt.series.empty)
props = $.extend(true, props, env.opt.series.empty.tooltip);
if (!props || !props.active)
return false;
if (props.frameProps) {
var color = common.getItemColor(env, serie, index);
if (color) {
props = common._clone(props);
common.colorize(env, props, [['frameProps', 'stroke']], color);
}
}
return props;
},
_fadeOut : function(env) {
env.tooltipContainer.fadeOut(env.opt.features.tooltip.fadeDelay);
},
onMouseEnter : function(env, serie, index, mouseAreaData) {
var props = this._getProps(env, serie, index, mouseAreaData);
if (!props) return false;
var tip = this.getTip(env, serie, index);
if (!tip) {
this._fadeOut(env);
return true;
}
//if (!env.opt.tooltips || (serie && (!env.opt.tooltips[serie] || !env.opt.tooltips[serie][index])) || (!serie && !env.opt.tooltips[index]))
// return this.onMouseExit(env, serie, index, mouseAreaData);
//var tip = serie ? env.opt.tooltips[serie][index] : env.opt.tooltips[index];
env.tooltipContainer.css(this._prepareShow(env, props, mouseAreaData, tip)).fadeIn(env.opt.features.tooltip.fadeDelay);
return true;
},
onMouseChanged : function(env, serie, index, mouseAreaData) {
var props = this._getProps(env, serie, index, mouseAreaData);
if (!props) return false;
var tip = this.getTip(env, serie, index);
if (!tip) {
this._fadeOut(env);
return true;
}
/*if (!env.opt.tooltips || (serie && (!env.opt.tooltips[serie] || !env.opt.tooltips[serie][index])) || (!serie && !env.opt.tooltips[index]))
return this.onMouseExit(env, serie, index, mouseAreaData);
var tip = serie ? env.opt.tooltips[serie][index] : env.opt.tooltips[index];*/
env.tooltipContainer.clearQueue();
// NOTE: this is needed because sometimes we "fadeOut" during mouseChanged so we also have to fadeIn in that cases.
// For simplicity we always fadeIn every time.
env.tooltipContainer.fadeIn(env.opt.features.tooltip.fadeDelay);
// Nota: Non passo da animationStackPush, i tooltip non sono legati a piece
env.tooltipContainer.animate(this._prepareShow(env, props, mouseAreaData, tip), env.opt.features.tooltip.moveDelay, 'linear' /*swing*/);
return true;
},
onMouseExit : function(env, serie, index, mouseAreaData) {
var props = this._getProps(env, serie, index, mouseAreaData);
if (!props) return false;
this._fadeOut(env);
return true;
}
}
$.elycharts.featuresmanager.register($.elycharts.tooltipmanager, 20);
})(jQuery);
/********* Source File: src/elycharts_chart_line.js*********/
/**********************************************************************
* ELYCHARTS
* A Javascript library to generate interactive charts with vectorial graphics.
*
* Copyright (c) 2010-2014 Void Labs s.n.c. (http://void.it)
* Licensed under the MIT (http://creativecommons.org/licenses/MIT/) license.
**********************************************************************/
(function($) {
var common = $.elycharts.common;
/***********************************************************************
* CHART: LINE/BAR
**********************************************************************/
$.elycharts.line = {
init : function($env) {
},
_getColorizationKey : function($type) {
if ($type == 'line') return [
['plotProps', 'stroke'],
['dotProps', 'fill'],
['fillProps', 'fill']
]; else return [
['plotProps', 'stroke'],
['plotProps', 'fill']
];
},
draw : function(env) {
if (common.executeIfChanged(env, ['values', 'series'])) {
env.plots = {};
env.axis = { x : {} };
env.barno = 0;
env.indexCenter = 'line';
}
var opt = env.opt;
var plots = env.plots;
var axis = env.axis;
var paper = env.paper;
var values = env.opt.values;
var labels = env.opt.labels;
var i, cum, props, serie, plot, labelsCount;
// Valorizzazione di tutte le opzioni utili e le impostazioni interne di ogni grafico e dell'ambiente di lavoro
if (common.executeIfChanged(env, ['values', 'series'])) {
var idx = 0;
var prevVisibleSerie = false;
for (serie in values) {
plot = {
index : idx,
type : false,
visible : false
};
plots[serie] = plot;
if (values[serie]) {
props = common.areaProps(env, 'Series', serie);
plot.type = props.type;
if (props.type == 'bar')
env.indexCenter = 'bar';
if (props.visible) {
plot.visible = true;
if (!labelsCount || labelsCount < values[serie].length)
labelsCount = values[serie].length;
// Values
// showValues: manage NULL elements (doing an avg of near points) for line serie
var showValues = []
for (i = 0; i < values[serie].length; i++) {
var val = values[serie][i];
if (props.avgOverNulls && val == null) {
if (props.type == 'bar')
val = 0;
else {
for (var j = i + 1; j < values[serie].length && values[serie][j] == null; j++) {}
var next = j < values[serie].length ? values[serie][j] : null;
for (var k = i -1; k >= 0 && values[serie][k] == null; k--) {}
var prev = k >= 0 ? values[serie][k] : null;
val = next != null ? (prev != null ? (next * (i - k) + prev * (j - i)) / (j - k) : next) : prev;
}
}
showValues.push(val);
}
if (props.stacked && !(typeof props.stacked == 'string'))
props.stacked = prevVisibleSerie;
if (typeof props.stacked == 'undefined' || props.stacked == serie || props.stacked < 0 || !plots[props.stacked] || !plots[props.stacked].visible || plots[props.stacked].type != plot.type) {
// NOT Stacked
plot.ref = serie;
if (props.type == 'bar')
plot.barno = env.barno ++;
plot.from = [];
if (!props.cumulative)
plot.to = showValues;
else {
plot.to = [];
cum = 0;
for (i = 0; i < showValues.length; i++)
plot.to.push(cum += showValues[i] != null ? showValues[i] : 0);
}
for (i = 0; i < showValues.length; i++)
plot.from.push(plot.to[i] != null ? 0 : null);
} else {
// Stacked
plot.ref = props.stacked;
if (props.type == 'bar')
plot.barno = plots[props.stacked].barno;
plot.from = plots[props.stacked].stack;
plot.to = [];
cum = 0;
if (!props.cumulative)
for (i = 0; i < showValues.length; i++)
plot.to.push(plot.from[i] + (showValues[i] != null ? showValues[i] : 0));
else
for (i = 0; i < showValues.length; i++)
plot.to.push(plot.from[i] + (cum += (showValues[i] != null ? showValues[i] : 0)));
plots[props.stacked].stack = plot.to;
}
plot.stack = plot.to;
plot.max = Math.max.apply(Math, plot.from.concat(plot.to));
plot.min = Math.min.apply(Math, plot.from.concat(plot.to));
// Assi (DEP: values, series)
if (props.axis) {
if (!axis[props.axis])
axis[props.axis] = { plots : [] };
axis[props.axis].plots.push(serie);
if (typeof axis[props.axis].max == 'undefined')
axis[props.axis].max = plot.max;
else
axis[props.axis].max = Math.max(axis[props.axis].max, plot.max);
if (typeof axis[props.axis].min == 'undefined')
axis[props.axis].min = plot.min;
else
axis[props.axis].min = Math.min(axis[props.axis].min, plot.min);
}
prevVisibleSerie = serie;
}
}
}
}
// Labels normalization (if not set or less than values)
if (!labels)
labels = [];
while (labelsCount > labels.length)
labels.push(null);
labelsCount = labels.length;
env.opt.labels = labels;
// Prepare axis scale (values, series, axis)
if (common.executeIfChanged(env, ['values', 'series', 'axis'])) {
for (var lidx in axis) {
props = common.areaProps(env, 'Axis', lidx);
axis[lidx].props = props;
if (typeof props.max != 'undefined')
axis[lidx].max = props.max;
if (typeof props.min != 'undefined')
axis[lidx].min = props.min;
if (axis[lidx].min == axis[lidx].max)
axis[lidx].max = axis[lidx].min + 1;
if (props.normalize && props.normalize > 0) {
var v = Math.abs(axis[lidx].max);
if (axis[lidx].min && Math.abs(axis[lidx].min) > v)
v = Math.abs(axis[lidx].min);
if (v) {
var basev = Math.floor(Math.log(v)/Math.LN10) - (props.normalize - 1);
// NOTE: On firefox Math.pow(10, -X) sometimes results in number noise (0.89999...), it's better to do 1/Math.pow(10,X)
basev = basev >= 0 ? Math.pow(10, basev) : 1 / Math.pow(10, -basev);
v = Math.ceil(v / basev / (opt.features.grid.ny ? opt.features.grid.ny : 1)) * basev * (opt.features.grid.ny ? opt.features.grid.ny : 1);
// Calculation above, with decimal number sometimes insert some noise in numbers (eg: 8.899999... instead of 0.9), so i need to round result with proper precision
v = Math.round(v / basev) * basev;
// I need to store the normalization base for further roundin (eg: in axis label, sometimes calculation results in "number noise", so i need to round them with proper precision)
axis[lidx].normalizationBase = basev;
if (axis[lidx].max)
axis[lidx].max = Math.ceil(axis[lidx].max / v) * v;
if (axis[lidx].min)
axis[lidx].min = Math.floor(axis[lidx].min / v) * v;
}
}
if (axis[lidx].plots)
for (var ii = 0; ii < axis[lidx].plots.length; ii++) {
plots[axis[lidx].plots[ii]].max = axis[lidx].max;
plots[axis[lidx].plots[ii]].min = axis[lidx].min;
}
}
}
var pieces = [];
this.grid(env, pieces);
// DEP: *
var deltaX = (env.width - opt.margins[3] - opt.margins[1]) / (labels.length > 1 ? labels.length - 1 : 1);
var deltaBarX = (env.width - opt.margins[3] - opt.margins[1]) / (labels.length > 0 ? labels.length : 1);
for (serie in values) {
props = common.areaProps(env, 'Series', serie);
plot = plots[serie];
common.colorize(env, props, this._getColorizationKey(props.type), common.getItemColor(env, serie));
// TODO Settare una props in questo modo potrebbe incasinare la gestione degli update parziali (se iso "lineCenter: auto" e passo da un grafico con indexCenter = bar a uno con indexCenter = line)
if (props.lineCenter && props.lineCenter == 'auto')
props.lineCenter = (env.indexCenter == 'bar');
else if (props.lineCenter && env.indexCenter == 'line')
env.indexCenter = 'bar';
if (values[serie] && props.visible) {
var deltaY = (env.height - opt.margins[2] - opt.margins[0]) / (plot.max - plot.min);
if (props.type == 'line') {
// LINE CHART
var linePath = [ 'LINE', [], props.rounded ];
var fillPath = [ 'LINEAREA', [], [], props.rounded ];
var dotPieces = [];
for (i = 0, ii = labels.length; i < ii; i++)
if (plot.to.length > i) {
var indexProps = common.areaProps(env, 'Series', serie, i);
common.colorize(env, indexProps, this._getColorizationKey(props.type), common.getItemColor(env, serie, i));
var x = Math.round((props.lineCenter ? deltaBarX / 2 : 0) + opt.margins[3] + i * (props.lineCenter ? deltaBarX : deltaX));
var y = null;
if (plot.to[i] != null) {
var d = plot.to[i] > plot.max ? plot.max : (plot.to[i] < plot.min ? plot.min : plot.to[i]);
y = Math.round(env.height - opt.margins[2] - deltaY * (d - plot.min));
}
var yy = null;
if (plot.from[i] != null) {
var dd = plot.from[i] > plot.max ? plot.max : (plot.from[i] < plot.min ? plot.min : plot.from[i]);
yy = Math.round(env.height - opt.margins[2] - deltaY * (dd - plot.min)) + (Raphael.VML ? 1 : 0);
}
linePath[1].push([x, y]);
if (props.fill) {
fillPath[1].push([x, y]);
fillPath[2].push([x, yy]);
}
if (indexProps.dot) {
if (values[serie][i] == null && !indexProps.dotShowOnNull)
dotPieces.push({path : false, attr : false});
else
dotPieces.push({path : [ [ 'CIRCLE', x, y, indexProps.dotProps.size ] ], attr : indexProps.dotProps}); // TODO Size should not be in dotProps (not an svg props)
}
}
if (props.fill)
pieces.push({ section : 'Series', serie : serie, subSection : 'Fill', path : [ fillPath ], attr : props.fillProps });
else
pieces.push({ section : 'Series', serie : serie, subSection : 'Fill', path : false, attr : false });
pieces.push({ section : 'Series', serie : serie, subSection : 'Plot', path : [ linePath ], attr : props.plotProps , mousearea : 'pathsteps'});
if (dotPieces.length)
pieces.push({ section : 'Series', serie : serie, subSection : 'Dot', paths : dotPieces });
else
pieces.push({ section : 'Series', serie : serie, subSection : 'Dot', path : false, attr : false });
} else {
pieceBar = [];
// BAR CHART
for (i = 0, ii = labels.length; i < ii; i++)
if (plot.to.length > i) {
if (plot.from[i] != plot.to[i]) {
var indexProps = common.areaProps(env, 'Series', serie, i);
common.colorize(env, indexProps, this._getColorizationKey(props.type), common.getItemColor(env, serie, i));
var bwid = Math.floor((deltaBarX - opt.barMargins) / (1 + (env.barno - 1) * (100 - opt.barOverlapPerc) / 100));
var bpad = bwid * (100 - props.barWidthPerc) / 200;
var boff = opt.barMargins / 2 + plot.barno * (bwid * (100 - opt.barOverlapPerc) / 100);
var x1 = Math.floor(opt.margins[3] + i * deltaBarX + boff + bpad);
var y1 = Math.round(env.height - opt.margins[2] - deltaY * (plot.to[i] - plot.min));
var y2 = Math.round(env.height - opt.margins[2] - deltaY * (plot.from[i] - plot.min));
pieceBar.push({path : [ [ 'RECT', x1, y1, x1 + bwid - bpad * 2, y2 ] ], attr : indexProps.plotProps });
} else
pieceBar.push({path : false, attr : false });
}
if (pieceBar.length)
pieces.push({ section : 'Series', serie : serie, subSection : 'Plot', paths: pieceBar, mousearea : 'paths' });
else
pieces.push({ section : 'Series', serie : serie, subSection : 'Plot', path: false, attr: false, mousearea : 'paths' });
}
} else {
// Grafico non visibile / senza dati, deve comunque inserire i piece vuoti (NELLO STESSO ORDINE SOPRA!)
if (props.type == 'line')
pieces.push({ section : 'Series', serie : serie, subSection : 'Fill', path : false, attr : false });
pieces.push({ section : 'Series', serie : serie, subSection : 'Plot', path: false, attr: false, mousearea : 'paths' });
if (props.type == 'line')
pieces.push({ section : 'Series', serie : serie, subSection : 'Dot', path : false, attr : false });
}
}
return pieces;
},
grid : function(env, pieces) {
// DEP: axis, [=> series, values], labels, margins, width, height, grid*
if (common.executeIfChanged(env, ['values', 'series', 'axis', 'labels', 'margins', 'width', 'height', 'features.grid'])) {
var opt = env.opt;
var props = env.opt.features.grid;
var paper = env.paper;
var axis = env.axis;
var labels = env.opt.labels;
var deltaX = (env.width - opt.margins[3] - opt.margins[1]) / (labels.length > 1 ? labels.length - 1 : 1);
var deltaBarX = (env.width - opt.margins[3] - opt.margins[1]) / (labels.length > 0 ? labels.length : 1);
var i, j, x, y, lw, labx, laby, labe, val, txt;
// Label X axis
var paths = [];
var labelsCenter = props.labelsCenter;
if (labelsCenter == 'auto')
labelsCenter = (env.indexCenter == 'bar');
if (axis.x && axis.x.props.labels) {
// used in case of labelsHideCovered, contains a "rotated" representation of the rect coordinates occupied by the last shown label
var lastShownLabelRect = false;
// labelsAnchor is "auto" by default. Can be "start","middle" or "end". If "auto" then it is automatically set depending on labelsRotate.
var labelsAnchor = axis.x.props.labelsAnchor || 'auto';
// Automatic labelsAnchor is "middle" on no rotation, otherwise the anchor is the higher side of the label.
if (labelsAnchor == 'auto')
labelsAnchor = axis.x.props.labelsRotate > 0 ? "start" : (axis.x.props.labelsRotate == 0 ? "middle" : "end");
// labelsPos is "auto" by default. Can be "start", "middle" or "end". If "auto" then it is automatically set depending on labelsCenter and labelsRotate and labelsAnchor.
var labelsPos = axis.x.props.labelsPos || 'auto';
// in labelsCenter (bar) it is middle when there is no rotation, equals to labelsAnchor on rotation.
// in !labelsCenter (line) is is always 'start';
if (labelsPos == 'auto')
labelsPos = labelsCenter ? (axis.x.props.labelsRotate == 0 ? labelsAnchor : 'middle') : 'start';
for (i = 0; i < labels.length; i++)
if ((typeof labels[i] != 'boolean' && labels[i] != null) || labels[i]) {
if (!axis.x.props.labelsSkip || i >= axis.x.props.labelsSkip) {
val = labels[i];
if (axis.x.props.labelsFormatHandler)
val = axis.x.props.labelsFormatHandler(val);
txt = (axis.x.props.prefix ? axis.x.props.prefix : "") + val + (axis.x.props.suffix ? axis.x.props.suffix : "");
labx = opt.margins[3] + i * (labelsCenter ? deltaBarX : deltaX) + (axis.x.props.labelsMargin ? axis.x.props.labelsMargin : 0);
if (labelsPos == 'middle') labx += (labelsCenter ? deltaBarX : deltaX) / 2;
if (labelsPos == 'end') labx += (labelsCenter ? deltaBarX : deltaX);
laby = env.height - opt.margins[2] + axis.x.props.labelsDistance;
labe = paper.text(labx, laby, txt).attr(axis.x.props.labelsProps).toBack();
labe.attr({"text-anchor" : labelsAnchor});
// will contain the boundingbox size, or false if it is hidden.
var boundingbox = false;
var bbox = labe.getBBox();
var p1 = {x: bbox.x, y: bbox.y};
var p2 = {x: bbox.x+bbox.width, y: bbox.y+bbox.height};
var o1 = {x: labx, y: laby};
rotate = function (p, rad) {
var X = p.x * Math.cos(rad) - p.y * Math.sin(rad),
Y = p.x * Math.sin(rad) + p.y * Math.cos(rad);
return {x: X, y: Y};
};
// calculate collision between non rotated rects with vertext p1-p2 and t1-t2
// this algorythm works only for horizontal rects (alpha = 0)
// "dist" is the length added as a margin to the rects before collision detection
collide = function(r1,r2,dist) {
xor = function(a,b) {
return ( a || b ) && !( a && b );
}
if (r1.alpha != r2.alpha) throw "collide doens't support rects with different rotations";
var r1p1r = rotate({x: r1.p1.x-dist, y:r1.p1.y-dist}, -r1.alpha);
var r1p2r = rotate({x: r1.p2.x+dist, y:r1.p2.y+dist}, -r1.alpha);
var r2p1r = rotate({x: r2.p1.x-dist, y:r2.p1.y-dist}, -r2.alpha);
var r2p2r = rotate({x: r2.p2.x+dist, y:r2.p2.y+dist}, -r2.alpha);
return !xor(Math.min(r1p1r.x,r1p2r.x) > Math.max(r2p1r.x,r2p2r.x), Math.max(r1p1r.x,r1p2r.x) < Math.min(r2p1r.x,r2p2r.x)) &&
!xor(Math.min(r1p1r.y,r1p2r.y) > Math.max(r2p1r.y,r2p2r.y), Math.max(r1p1r.y,r1p2r.y) < Math.min(r2p1r.y,r2p2r.y));
}
// compute equivalent orizontal rotated rect
rotated = function(rect, origin, alpha) {
translate = function (p1, p2) {
return {x: p1.x+p2.x, y: p1.y+p2.y};
};
negate = function(p1) {
return {x: -p1.x, y: -p1.y};
};
var p1trt = translate(rotate(translate(rect.p1,negate(origin)), alpha),origin);
var p2trt = translate(rotate(translate(rect.p2,negate(origin)), alpha),origin);
return { p1: p1trt, p2: p2trt, alpha: rect.alpha+alpha };
}
bbox = function(rect) {
if (rect.alpha == 0) {
return { x: rect.p1.x, y: rect.p1.y, width: rect.p2.x-rect.p1.x, height: rect.p2.y-rect.p1.y };
} else {
var points = [];
points.push({ x: 0, y: 0 });
points.push({ x: rect.p2.x-rect.p1.x, y: 0 });
points.push({ x: 0, y: rect.p2.y-rect.p1.y });
points.push({ x: rect.p2.x-rect.p1.x, y: rect.p2.y-rect.p1.y });
var bb = [];
bb['left'] = 0; bb['right'] = 0; bb['top'] = 0; bb['bottom'] = 0;
for (_px = 0; _px < points.length; _px++) {
var p = points[_px];
var newX = parseInt((p.x * Math.cos(rect.alpha)) + (p.y * Math.sin(rect.alpha)));
var newY = parseInt((p.x * Math.sin(rect.alpha)) + (p.y * Math.cos(rect.alpha)));
bb['left'] = Math.min(bb['left'], newX);
bb['right'] = Math.max(bb['right'], newX);
bb['top'] = Math.min(bb['top'], newY);
bb['bottom'] = Math.max(bb['bottom'], newY);
}
var newWidth = parseInt(Math.abs(bb['right'] - bb['left']));
var newHeight = parseInt(Math.abs(bb['bottom'] - bb['top']));
var newX = ((rect.p1.x + rect.p2.x) / 2) - newWidth / 2;
var newY = ((rect.p1.y + rect.p2.y) / 2) - newHeight / 2;
return { x: newX, y: newY, width: newWidth, height: newHeight };
}
}
var alpha = Raphael.rad(axis.x.props.labelsRotate);
// compute used "rect" so to be able to check if there is overlapping with previous ones.
var rect = rotated({p1: p1, p2: p2, alpha: 0}, o1, alpha);
//console.log('bbox ',p1, p2, rect, props.nx, val, rect.p1, rect.p2, rect.alpha, boundingbox, env.width);
// se collide con l'ultimo mostrato non lo mostro.
var dist = axis.x.props.labelsMarginRight ? axis.x.props.labelsMarginRight / 2 : 0;
if (axis.x.props.labelsHideCovered && lastShownLabelRect && collide(rect, lastShownLabelRect, dist)) {
labe.hide();
// labels[i] = false;
} else {
boundingbox = bbox(rect);
// Manage label overflow
if (props.nx == 'auto' && (boundingbox.x < 0 || boundingbox.x+boundingbox.width > env.width)) {
labe.hide();
// labels[i] = false;
} else {
lastShownLabelRect = rect;
}
}
// Apply rotation to the element.
if (axis.x.props.labelsRotate) {
if (Raphael.animation) {
labe.transform(Raphael.format('r{0},{1},{2}', axis.x.props.labelsRotate, labx, laby)).toBack();
} else {
labe.rotate(axis.x.props.labelsRotate, labx, laby).toBack();
}
}
paths.push({ path : [ [ 'RELEMENT', labe ] ], attr : false });
}
}
}
pieces.push({ section : 'Axis', serie : 'x', subSection : 'Label', paths : paths });
// Title X Axis
if (axis.x && axis.x.props.title) {
x = opt.margins[3] + Math.floor((env.width - opt.margins[1] - opt.margins[3]) / 2);
y = env.height - opt.margins[2] + axis.x.props.titleDistance * (Raphael.VML ? axis.x.props.titleDistanceIE : 1);
//paper.text(x, y, axis.x.props.title).attr(axis.x.props.titleProps);
pieces.push({ section : 'Axis', serie : 'x', subSection : 'Title', path : [ [ 'TEXT', axis.x.props.title, x, y ] ], attr : axis.x.props.titleProps });
} else
pieces.push({ section : 'Axis', serie : 'x', subSection : 'Title', path : false, attr : false });
// Label + Title L/R Axis
for (var jj in ['l', 'r']) {
j = ['l', 'r'][jj];
if (axis[j] && axis[j].props.labels && props.ny) {
paths = [];
for (i = axis[j].props.labelsSkip ? axis[j].props.labelsSkip : 0; i <= props.ny; i++) {
var deltaY = (env.height - opt.margins[2] - opt.margins[0]) / props.ny;
// TODO we should never set "props". We should use local variables for derived value (so to correctly deal with updates)
if (j == 'r') {
labx = env.width - opt.margins[1] + axis[j].props.labelsDistance;
if (!axis[j].props.labelsProps["text-anchor"])
axis[j].props.labelsProps["text-anchor"] = "start";
} else {
labx = opt.margins[3] - axis[j].props.labelsDistance;
if (!axis[j].props.labelsProps["text-anchor"])
axis[j].props.labelsProps["text-anchor"] = "end";
}
if (axis[j].props.labelsAnchor && axis[j].props.labelsAnchor != 'auto')
axis[j].props.labelsProps["text-anchor"] = axis[j].props.labelsAnchor;
// NOTE: Parenthesis () around division are useful to keep right number precision
val = (axis[j].min + (i * ((axis[j].max - axis[j].min) / props.ny)));
// Rounding with proper precision for "number sharpening"
if (axis[j].normalizationBase)
// I use (1 / ( 1 / norm ) ) to avoid some noise
val = Math.round(val / axis[j].normalizationBase) / ( 1 / axis[j].normalizationBase );
if (axis[j].props.labelsFormatHandler)
val = axis[j].props.labelsFormatHandler(val);
if (axis[j].props.labelsCompactUnits)
val = common.compactUnits(val, axis[j].props.labelsCompactUnits);
txt = (axis[j].props.prefix ? axis[j].props.prefix : "") + val + (axis[j].props.suffix ? axis[j].props.suffix : "");
laby = env.height - opt.margins[2] - i * deltaY;
//var labe = paper.text(labx, laby + (axis[j].props.labelsMargin ? axis[j].props.labelsMargin : 0), txt).attr(axis[j].props.labelsProps).toBack();
paths.push( { path : [ [ 'TEXT', txt, labx, laby + (axis[j].props.labelsMargin ? axis[j].props.labelsMargin : 0) ] ], attr : axis[j].props.labelsProps });
}
pieces.push({ section : 'Axis', serie : j, subSection : 'Label', paths : paths });
} else
pieces.push({ section : 'Axis', serie : j, subSection : 'Label', paths : [] });
if (axis[j] && axis[j].props.title) {
if (j == 'r')
x = env.width - opt.margins[1] + axis[j].props.titleDistance * (Raphael.VML ? axis[j].props.titleDistanceIE : 1);
else
x = opt.margins[3] - axis[j].props.titleDistance * (Raphael.VML ? axis[j].props.titleDistanceIE : 1);
//paper.text(x, opt.margins[0] + Math.floor((env.height - opt.margins[0] - opt.margins[2]) / 2), axis[j].props.title).attr(axis[j].props.titleProps).attr({rotation : j == 'l' ? 270 : 90});
var attr = common._clone(axis[j].props.titleProps);
var rotation = j == 'l' ? 270 : 90;
var y = opt.margins[0] + Math.floor((env.height - opt.margins[0] - opt.margins[2]) / 2);
// Raphael 2 does not support .rotation
if (Raphael.animation) {
var labe = paper.text(x, y, axis[j].props.title).attr(attr).transform(Raphael.format('r{0}', rotation)).toBack();
pieces.push({ section : 'Axis', serie : j, subSection : 'Title', path : [ [ 'RELEMENT', labe ] ], attr : false });
} else {
attr.rotation = rotation;
pieces.push({ section : 'Axis', serie : j, subSection : 'Title', path : [ [ 'TEXT', axis[j].props.title, x, y ] ], attr : attr });
}
} else
pieces.push({ section : 'Axis', serie : j, subSection : 'Title', path : false, attr : false });
}
// Grid
if (props.nx || props.ny) {
var path = [], bandsH = [], bandsV = [],
nx = props.nx == 'auto' ? (labelsCenter ? labels.length : labels.length - 1) : props.nx,
ny = props.ny,
rowHeight = (env.height - opt.margins[2] - opt.margins[0]) / (ny ? ny : 1),
columnWidth = (env.width - opt.margins[1] - opt.margins[3]) / (nx ? nx : 1),
forceBorderX1 = typeof props.forceBorder == 'object' ? props.forceBorder[3] : props.forceBorder,
forceBorderX2 = typeof props.forceBorder == 'object' ? props.forceBorder[1] : props.forceBorder,
forceBorderY1 = typeof props.forceBorder == 'object' ? props.forceBorder[0] : props.forceBorder,
forceBorderY2 = typeof props.forceBorder == 'object' ? props.forceBorder[2] : props.forceBorder,
drawH = ny > 0 ? (typeof props.draw == 'object' ? props.draw[0] : props.draw) : false,
drawV = nx > 0 ? typeof props.draw == 'object' ? props.draw[1] : props.draw : false;
if (ny > 0)
for (i = 0; i < ny + 1; i++) {
if (
forceBorderY1 && i == 0 || // Show top line only if forced
forceBorderY2 && i == ny || // Show bottom line only if forced
drawH && i > 0 && i < ny // Show other lines if draw = true
) {
path.push(["M", opt.margins[3] - props.extra[3], opt.margins[0] + Math.round(i * rowHeight) ]);
path.push(["L", env.width - opt.margins[1] + props.extra[1], opt.margins[0] + Math.round(i * rowHeight)]);
}
if (i < ny) {
if (i % 2 == 0 && props.evenHProps || i % 2 == 1 && props.oddHProps)
bandsH.push({path : [ [ 'RECT',
opt.margins[3] - props.extra[3], opt.margins[0] + Math.round(i * rowHeight), // x1, y1
env.width - opt.margins[1] + props.extra[1], opt.margins[0] + Math.round((i + 1) * rowHeight) // x2, y2
] ], attr : i % 2 == 0 ? props.evenHProps : props.oddHProps });
else
bandsH.push({ path : false, attr: false})
}
}
for (i = 0; i < nx + 1; i++) {
if (
forceBorderX1 && i == 0 || // Always show first line if forced
forceBorderX2 && i == nx || // Always show last line if forced
drawV && ( // To show other lines draw must be true
(props.nx != 'auto' && i > 0 && i < nx) || // If nx = [number] show other lines (first and last are managed above with forceBorder)
(props.nx == 'auto' && (typeof labels[i] != 'boolean' || labels[i])) // if nx = 'auto' show all lines if a label is associated
)
// Show all lines if props.nx is a number, or if label != false, AND draw must be true
) {
path.push(["M", opt.margins[3] + Math.round(i * columnWidth), opt.margins[0] - props.extra[0] ]); //(t ? props.extra[0] : 0)]);
path.push(["L", opt.margins[3] + Math.round(i * columnWidth), env.height - opt.margins[2] + props.extra[2] ]); //(t ? props.extra[2] : 0)]);
}
if (i < nx) {
if (i % 2 == 0 && props.evenVProps || i % 2 == 1 && props.oddVProps)
bandsV.push({path : [ [ 'RECT',
opt.margins[3] + Math.round(i * columnWidth), opt.margins[0] - props.extra[0], // x1, y1
opt.margins[3] + Math.round((i + 1) * columnWidth), env.height - opt.margins[2] + props.extra[2], // x2, y2
] ], attr : i % 2 == 0 ? props.evenVProps : props.oddVProps });
else
bandsV.push({ path : false, attr: false})
}
}
pieces.push({ section : 'Grid', path : path.length ? path : false, attr : path.length ? props.props : false });
pieces.push({ section : 'GridBandH', paths : bandsH });
pieces.push({ section : 'GridBandV', paths : bandsV });
var tpath = [];
// Ticks asse X
if (props.ticks.active && (typeof props.ticks.active != 'object' || props.ticks.active[0])) {
for (i = 0; i < nx + 1; i++) {
if (props.nx != 'auto' || typeof labels[i] != 'boolean' || labels[i]) {
tpath.push(["M", opt.margins[3] + Math.round(i * columnWidth), env.height - opt.margins[2] - props.ticks.size[1] ]);
tpath.push(["L", opt.margins[3] + Math.round(i * columnWidth), env.height - opt.margins[2] + props.ticks.size[0] ]);
}
}
}
// Ticks asse L
if (props.ticks.active && (typeof props.ticks.active != 'object' || props.ticks.active[1]))
for (i = 0; i < ny + 1; i++) {
tpath.push(["M", opt.margins[3] - props.ticks.size[0], opt.margins[0] + Math.round(i * rowHeight) ]);
tpath.push(["L", opt.margins[3] + props.ticks.size[1], opt.margins[0] + Math.round(i * rowHeight)]);
}
// Ticks asse R
if (props.ticks.active && (typeof props.ticks.active != 'object' || props.ticks.active[2]))
for (i = 0; i < ny + 1; i++) {
tpath.push(["M", env.width - opt.margins[1] - props.ticks.size[1], opt.margins[0] + Math.round(i * rowHeight) ]);
tpath.push(["L", env.width - opt.margins[1] + props.ticks.size[0], opt.margins[0] + Math.round(i * rowHeight)]);
}
pieces.push({ section : 'Ticks', path : tpath.length ? tpath : false, attr : tpath.length ? props.ticks.props : false });
}
}
}
}
})(jQuery);
/********* Source File: src/elycharts_chart_pie.js*********/
/**********************************************************************
* ELYCHARTS
* A Javascript library to generate interactive charts with vectorial graphics.
*
* Copyright (c) 2010-2014 Void Labs s.n.c. (http://void.it)
* Licensed under the MIT (http://creativecommons.org/licenses/MIT/) license.
**********************************************************************/
(function($) {
var common = $.elycharts.common;
/***********************************************************************
* CHART: PIE
**********************************************************************/
$.elycharts.pie = {
init : function($env) {
},
draw : function(env) {
//var paper = env.paper;
var opt = env.opt;
var w = env.width - env.opt.margins[1] - env.opt.margins[3];
var h = env.height - env.opt.margins[0] - env.opt.margins[2];
var r = env.opt.r ? env.opt.r : Math.floor((w < h ? w : h) / 2 * (env.opt.rPerc ? env.opt.rPerc / 100 : 0.8));
var cx = (env.opt.cx ? env.opt.cx : Math.floor(w / 2)) + env.opt.margins[3];
var cy = (env.opt.cy ? env.opt.cy : Math.floor(h / 2)) + env.opt.margins[0];
var cnt = 0, i, ii, serie, plot, props;
for (serie in opt.values) {
plot = {
visible : false,
total : 0,
values : []
};
env.plots[serie] = plot;
var serieProps = common.areaProps(env, 'Series', serie);
common.colorize(env, serieProps, [['plotProps','stroke'],['plotProps','fill']], common.getItemColor(env, serie));
if (serieProps.visible) {
plot.visible = true;
cnt ++;
plot.values = opt.values[serie];
for (i = 0, ii = plot.values.length; i < ii; i++)
if (plot.values[i] > 0) {
props = common.areaProps(env, 'Series', serie, i);
common.colorize(env, props, [['plotProps','stroke'],['plotProps','fill']], common.getItemColor(env, serie, i));
if (typeof props.inside == 'undefined' || props.inside < 0)
plot.total += plot.values[i];
}
for (i = 0; i < ii; i++)
if (plot.values[i] < plot.total * opt.valueThresold) {
plot.total = plot.total - plot.values[i];
plot.values[i] = 0;
}
}
}
var rstep = r / cnt;
var rstart = -rstep, rend = 0;
var pieces = [];
for (serie in opt.values) {
plot = env.plots[serie];
var paths = [];
if (plot.visible) {
rstart += rstep;
rend += rstep;
var angle = env.opt.startAngle, angleplus = 0, anglelimit = 0;
if (plot.total == 0) {
env.emptySeries = true;
props = common.areaProps(env, 'Series', 'empty');
common.colorize(env, props, [['plotProps','stroke'],['plotProps','fill']], common.getItemColor(env, serie));
paths.push({ path : [ [ 'CIRCLE', cx, cy, r ] ], attr : props.plotProps });
} else {
env.emptySeries = false;
for (i = 0, ii = plot.values.length; i < ii; i++) {
var value = plot.values[i];
if (value > 0) {
props = common.areaProps(env, 'Series', serie, i);
common.colorize(env, props, [['plotProps','stroke'],['plotProps','fill']], common.getItemColor(env, serie, i));
if (typeof props.inside == 'undefined' || props.inside < 0) {
angle += anglelimit;
angleplus = 360 * value / plot.total;
anglelimit = angleplus;
} else {
angleplus = 360 * values[props.inside] / plot.total * value / values[props.inside];
}
var rrstart = rstart, rrend = rend;
if (props.r) {
if (props.r > 0) {
if (props.r <= 1)
rrend = rstart + rstep * props.r;
else
rrend = rstart + props.r;
} else {
if (props.r >= -1)
rrstart = rstart + rstep * (-props.r);
else
rrstart = rstart - props.r;
}
}
if (!env.opt.clockwise)
paths.push({ path : [ [ 'SLICE', cx, cy, rrend, rrstart, angle, angle + angleplus ] ], attr : props.plotProps });
else
paths.push({ path : [ [ 'SLICE', cx, cy, rrend, rrstart, - angle - angleplus, - angle ] ], attr : props.plotProps });
} else
paths.push({ path : false, attr : false });
}
}
} else {
// Even if serie is not visible it's better to put some empty path (for better transitions). It's not mandatory, just better
if (opt.values[serie] && opt.values[serie].length)
for (i = 0, ii = opt.values[serie].length; i < ii; i++)
paths.push({ path : false, attr : false });
}
pieces.push({ section : 'Series', serie : serie, subSection : 'Plot', paths : paths , mousearea : 'paths'});
}
return pieces;
}
}
})(jQuery);