mirror of
https://github.com/nzbget/nzbget.git
synced 2026-01-20 03:47:48 -05:00
3988 lines
165 KiB
JavaScript
Vendored
3988 lines
165 KiB
JavaScript
Vendored
/********* 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);
|