diff --git a/SFDC-BrewApp/LICENSE b/SFDC-BrewApp/LICENSE new file mode 100644 index 0000000..e027868 --- /dev/null +++ b/SFDC-BrewApp/LICENSE @@ -0,0 +1,37 @@ +Portions Copyright (C) 2012 Research In Motion Limited. +Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +All rights reserved. +Contact: Research In Motion Ltd. (http://www.rim.com/company/contact/) +Contact: Nokia Corporation (qt-info@nokia.com) + +This file is part of the examples of the BB10 Platform and is derived +from a similar file that is part of the Qt Toolkit. + +You may use this file under the terms of the BSD license as follows: + +"Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name of Research In Motion, nor the name of Nokia + Corporation and its Subsidiary(-ies), nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior written + permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." diff --git a/SFDC-BrewApp/Makefile b/SFDC-BrewApp/Makefile new file mode 100755 index 0000000..0fc4e75 --- /dev/null +++ b/SFDC-BrewApp/Makefile @@ -0,0 +1,6 @@ +QMAKE_TARGET = brewmaster +PROJECT_DIR := $(dir $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))) +I18N_DIR := $(PROJECT_DIR)/translations + +include mk/cs-base.mk + diff --git a/SFDC-BrewApp/Q10_Brewer_Splash.png b/SFDC-BrewApp/Q10_Brewer_Splash.png new file mode 100755 index 0000000..60bfa21 Binary files /dev/null and b/SFDC-BrewApp/Q10_Brewer_Splash.png differ diff --git a/SFDC-BrewApp/README.md b/SFDC-BrewApp/README.md new file mode 100644 index 0000000..f041cc4 --- /dev/null +++ b/SFDC-BrewApp/README.md @@ -0,0 +1,39 @@ +SFDC-BrewApp +============ + +Salesforce.com BlackBerry 10 native demo app + +This application demonstrates how you can quickly create a Cascades BlackBerry 10 application that integrates with your SalesForce.com instance. + +This sample provides a SFDCSetupv04.docx, MS-Word document, that shows you how to get up and running quickly in the SFDC Dev Cloud. +Sample also includes most of the workflows you will need, already coded for you: + + * oAuth2.0 workflow + * Account LookUp + * Product + Volume LookUp + * BlackBerry Hub integration + * Invocation sample code + * Notifications + * ... and tons more + +======================================================================== +Requirements: + +BlackBerry 10 Native SDK + +======================================================================== +Running the example: + +1. Clone the Sample repository. +2. Launch BlackBerry 10 Native SDK, and from the File menu, select Import. +3. Expand General, and select Existing Projects into Workspace. Click Next. +4. Browse to the location of your sample directory, and then click OK. +5. The sample project should display in the Projects section. + Click Finish to import the project into your workspace. +6. In the Project Explorer pane, Right-click the project (for example hellocascades) + and select Build Project. +7. In the Project Explorer pane, Right-click the project (for example hellocascades) + and select Run As > BlackBerry C/C++ Application. +8. The application will now install and launch on your device if not you might + have to set up your environment: + http://developer.blackberry.com/cascades/documentation/getting_started/setting_up.html diff --git a/SFDC-BrewApp/SFDCSetupv04.docx b/SFDC-BrewApp/SFDCSetupv04.docx new file mode 100644 index 0000000..eba8bc4 Binary files /dev/null and b/SFDC-BrewApp/SFDCSetupv04.docx differ diff --git a/SFDC-BrewApp/Z10_Brewer_Splash.png b/SFDC-BrewApp/Z10_Brewer_Splash.png new file mode 100755 index 0000000..02f0516 Binary files /dev/null and b/SFDC-BrewApp/Z10_Brewer_Splash.png differ diff --git a/SFDC-BrewApp/assets/.assets.index b/SFDC-BrewApp/assets/.assets.index new file mode 100755 index 0000000..44056af --- /dev/null +++ b/SFDC-BrewApp/assets/.assets.index @@ -0,0 +1,64 @@ +1 +62 +720x720/BarDetailsPage.qml +BarDetailsHeaderContainer.qml +BarDetailsPage.qml +BarMap.qml +BarsListView.qml +BarsMapView.qml +BeerStatsPage.qml +BreweryItemContainer.qml +chart/elycharts.js +chart/elycharts.min.js +chart/index.html +chart/jquery-1.8.3.min.js +chart/jquery-1.9.1.min.js +chart/jquery-migrate-1.1.1.min.js +chart/raphael-min.js +images/ActionBar_BBM.png +images/ActionBar_Call.png +images/ActionBar_Logout.png +images/ActionBar_Map.png +images/bars/0.png +images/bars/1.png +images/bars/10.png +images/bars/12.png +images/bars/2.png +images/bars/3.png +images/bars/4.png +images/bars/5.png +images/bars/6.png +images/bars/7.png +images/bars/8.png +images/beer/blackwater.png +images/beer/blank.png +images/beer/blonde-ale.png +images/beer/brown-ale.png +images/beer/ic_all.png +images/beer/olde-pelican.png +images/beer/pale-ale.png +images/beer/red-ale.png +images/divider.png +images/graph_background.png +images/header.png +images/header_blank.png +images/next.png +images/picture1.png +images/picture1br.png +images/picture1thumb.png +images/previous.png +images/pull_to_refresh.png +images/pullToRefreshArrow.png +images/ribbon_green.png +images/ribbon_red.png +images/ribbon_yellow.png +images/salesforce.png +images/ShareBBM.png +images/stats.png +images/wood_background.png +LoginPage.qml +main.qml +MainPage.qml +PullToRefresh.qml +RefreshableListView.qml +TouchContainer.qml diff --git a/SFDC-BrewApp/assets/720x720/BarDetailsPage.qml b/SFDC-BrewApp/assets/720x720/BarDetailsPage.qml new file mode 100644 index 0000000..d0c3883 --- /dev/null +++ b/SFDC-BrewApp/assets/720x720/BarDetailsPage.qml @@ -0,0 +1,294 @@ +/* Copyright (c) 2012 Research In Motion Limited. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + + +import bb.cascades 1.0 +import bb.system 1.0 +import bbmodel 1.0 + +Page { + id: detailPage + property real averageQuantity: 0 + + property variant item: null + property Page mapPage + + function getMapPage() { + if (! mapPage) { + mapPage = mapPageDefinition.createObject(); + } + return mapPage; + } + + onItemChanged: { + console.log("onItemChanged") + if (item != null) { + dataRequest.execute("/services/data/v27.0/query/?q=SELECT+%NAMESPACE%__Product__r.ProductCode,+%NAMESPACE%__quantity_remaining__c,+%NAMESPACE%__quantity__c,+%NAMESPACE%__Product__r.Name+FROM+%NAMESPACE%__Volume__c+WHERE+(%NAMESPACE%__Account__r.AccountNumber+='" + item.__Account__r.AccountNumber + "' and %NAMESPACE%__date__c > LAST_WEEK)") + } + } + + titleBar: TitleBar { kind: TitleBarKind.FreeForm + kindProperties: FreeFormTitleBarKindProperties { + content: Container { + background: headerBG.imagePaint + layoutProperties: StackLayoutProperties { + spaceQuota: 1.0 + } + layout: DockLayout { + } + leftPadding: 16 + Label { + verticalAlignment: VerticalAlignment.Center + text: item.__Account__r.Name + textStyle.fontSizeValue: 12 + textStyle.fontSize: FontSize.PointValue + } + } + } + } + Container { + + Container { + layout: DockLayout { + } + horizontalAlignment: HorizontalAlignment.Fill + verticalAlignment: VerticalAlignment.Fill + + Container { + id: indicatorContainer + layout: DockLayout { + } + horizontalAlignment: HorizontalAlignment.Fill + verticalAlignment: VerticalAlignment.Fill + ActivityIndicator { + id: myIndicator + preferredWidth: 100 + horizontalAlignment: HorizontalAlignment.Center + verticalAlignment: VerticalAlignment.Center + running: dataRequest.isLoading + visible: dataRequest.isLoading + } + } + Container { + id: listContainer + ListView { + id: listView + + property real avgQuantity : averageQuantity + horizontalAlignment: HorizontalAlignment.Fill + + dataModel: beerDataModel + + onTriggered: { + //indexPath + if (indexPath == 0) {} + else if (indexPath == 1) { + var page = detailPageDefinition.createObject(); + var beers = []; + + for (var i = 2; i < beerDataModel.size(); i ++) { + var beer = beerDataModel.value(i)["__Product__r"]; + var queryId = "'" + beer.ProductCode + "'"; + beers.push(queryId); + } + + page.bar = detailPage.item.__Account__r + page.beers = beers.join(","); + navigationPane.push(page); + } else { + var page = detailPageDefinition.createObject(); + var beer = dataModel.value(indexPath)["__Product__r"]; + page.bar = detailPage.item.__Account__r + page.beer = beer; + navigationPane.push(page); + } + } + function itemType(data, indexPath) { + if (indexPath == 0) return "header"; + else if (indexPath == 1) return "summary"; + return "item"; + } + function getItem() { + return detailPage.item; + } + function getApp() { + return Qt.app; + } + function getAverage() { + console.log(" getAverage = " + averageQuantity) + return averageQuantity; + } + function getColorWithBeerPercentage(percentage) { + if (percentage > 0.75) return Color.create("#73a039"); + if (percentage > 0.45) return Color.create("#e5d15c"); + return Color.create("#a53927"); + } + function getAccessoryImageWithBeerPercentage(percentage) { + if (percentage > 0.75) return "asset:///images/ribbon_green.png"; + if (percentage > 0.45) return "asset:///images/ribbon_yellow.png"; + return "asset:///images/ribbon_red.png"; + } + function launchBBM(pin) { + app.launchBBMChat(pin) + } + + listItemComponents: [ + ListItemComponent { + type: "header" + BarDetailsHeaderContainer { + item: ListItem.view.getItem() + myapp: ListItem.view.getApp() + } + }, + ListItemComponent { + type: "summary" + BreweryItemContainer { + imageView.imageSource: "asset:///images/beer/ic_all.png" + titleLabel.text: "All" + titleLabel.textStyle.fontSizeValue: 11 + accessoryImage: ListItem.view.getAccessoryImageWithBeerPercentage(ListItem.view.getAverage()) + percentageLabel.text: Math.floor(ListItem.view.getAverage() * 100) + "%" + percentageLabel.textStyle.color: ListItem.view.getColorWithBeerPercentage(ListItem.view.getAverage()) + } + }, + ListItemComponent { + type: "item" + + BreweryItemContainer { + property variant beerData: ListItem.data.__Product__r + imageView.imageSource: "asset:///images/beer/" + getImageName() + ".png" + titleLabel.text: beerData.Name + titleLabel.textStyle.fontSizeValue: 11 + accessoryImage: ListItem.view.getAccessoryImageWithBeerPercentage(ListItem.data.__quantity_remaining__c / ListItem.data.__quantity__c) + percentageLabel.text: Math.floor((ListItem.data.__quantity_remaining__c / ListItem.data.__quantity__c) * 100) + "%" + percentageLabel.textStyle.color: ListItem.view.getColorWithBeerPercentage(ListItem.data.__quantity_remaining__c / ListItem.data.__quantity__c) + + function getImageName() { + if (beerData.Name == "Blond Ale") return "blonde-ale"; + else if (beerData.Name == "Brown Ale") return "brown-ale"; + else if (beerData.Name == "Olde Pelican") return "olde-pelican"; + else if (beerData.Name == "Pale Ale") return "pale-ale"; + else if (beerData.Name == "Red Ale") return "red-ale"; + else return "blank.png" + } + } + } + ] + } + } + } + } + + actions: [ + ActionItem { + title: qsTr("Map") + ActionBar.placement: ActionBarPlacement.OnBar + imageSource: "asset:///images/ActionBar_Map.png" + enabled: { + if (item != null && item.__Account__r.__BillingLatitude__c && item.__Account__r.__BillingLongitude__c) { + true; + } else { + false; + } + } + onTriggered: { + var page = getMapPage(); + page.item = item + navigationPane.push(page); + page.setupMap() + } + }, + ActionItem { + title: qsTr("Call") + ActionBar.placement: ActionBarPlacement.OnBar + imageSource: "asset:///images/ActionBar_Call.png" + enabled: { + if (item != null && app.contactMap.Phone) { + true; + } else { + false; + } + } + onTriggered: { + dialNumber.initiateCall(app.contactMap.Phone); + } + }, + ActionItem { + title: qsTr("BBM") + ActionBar.placement: ActionBarPlacement.OnBar + imageSource: "asset:///images/ActionBar_BBM.png" + enabled: { + if (item != null && app.contactMap.__bbmpin__c) { + true; + } else { + false; + } + } + onTriggered: { + app.launchBBMChat(app.contactMap.__bbmpin__c) + } + } + ] + paneProperties: NavigationPaneProperties { + backButton: ActionItem { + onTriggered: { + navigationPane.pop() + } + } + } + + attachedObjects: [ + ImagePaintDefinition { + id: headerBG + repeatPattern: RepeatPattern.Fill + imageSource: "asset:///images/header_blank.png" + }, + ComponentDefinition { + id: detailPageDefinition + source: "BeerStatsPage.qml" + }, + ComponentDefinition { + id: mapPageDefinition + source: "BarMap.qml" + }, + Phone { + id: dialNumber + }, + DataRequest { + id: dataRequest + + onAverageCalculated: { + averageQuantity = result; + } + onDataReturned: { + console.log(result); + + var dataList = result["records"]; + beerDataModel.clear() + beerDataModel.append({itemType: "header"}) + beerDataModel.append({itemType: "summary (AKA *All* button, this is the first record in the list..... we just need a blank record so the list has 1 additional row.............)"}); + beerDataModel.append(dataList); + + } + }, + ArrayDataModel { + id: beerDataModel + }, + Header{ + id: summaryObj + objectName: "summary" + } + ] +} diff --git a/SFDC-BrewApp/assets/BarDetailsHeaderContainer.qml b/SFDC-BrewApp/assets/BarDetailsHeaderContainer.qml new file mode 100644 index 0000000..01a23ae --- /dev/null +++ b/SFDC-BrewApp/assets/BarDetailsHeaderContainer.qml @@ -0,0 +1,166 @@ +/* Copyright (c) 2012 Research In Motion Limited. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + + +import bb.cascades 1.0 +import bb.system 1.0 + +Container { + property variant item: null + property variant myapp: null + + background: woodBG.imagePaint + maxHeight: 366 + minHeight: 366 + horizontalAlignment: HorizontalAlignment.Fill + layoutProperties: StackLayoutProperties { + spaceQuota: 1.0 + } + + Container { + layout: StackLayout { + orientation: LayoutOrientation.LeftToRight + } + + leftPadding: 24 + topPadding: 24 + rightPadding: 24 + + Container { + maxWidth: 188 + maxHeight: 188 + ImageView { + id: imgView + imageSource: item != null ? "asset:///images/bars/" + item.__Account__r.AccountNumber + ".png" : '' + horizontalAlignment: HorizontalAlignment.Center + } + } + + Container { + layoutProperties: StackLayoutProperties { + spaceQuota: 1.0 + } + topMargin: 24 + leftMargin: 24 + Container { + layout: AbsoluteLayout { + } + Label { + layoutProperties: AbsoluteLayoutProperties { + positionY: 0 + } + text: item != null ? item.__Account__r.__Address__c : '' + textStyle { + base: SystemDefaults.TextStyles.TitleText + color: Color.White + } + } + Label { + layoutProperties: AbsoluteLayoutProperties { + positionY: 52 + } + text: item != null ? item.__Account__r.__City__c : '' + textStyle { + base: SystemDefaults.TextStyles.TitleText + color: Color.create("#beaa79") + } + } + Label { + layoutProperties: AbsoluteLayoutProperties { + positionY: 104 + } + text: item != null ? item.__Account__r.zipcode__c : '' + textStyle { + base: SystemDefaults.TextStyles.TitleText + color: Color.create("#beaa79") + } + } + TouchContainer { + layoutProperties: AbsoluteLayoutProperties { + positionY: 156 + } + Label { + id: contactPhone + text: item != null ? myapp.contactMap.Phone : '' + textStyle { + base: SystemDefaults.TextStyles.TitleText + color: Color.create("#beaa79") + } + } + onTriggered: { + if (myapp.contactMap.Phone) app.launchPhone(myapp.contactMap.Phone); + //dialNumber.initiateCall(item["__phone__c"]); + else myapp.showToast("Contact does not have a phone") + } + } + } + } + } + Container { + layout: StackLayout { + orientation: LayoutOrientation.LeftToRight + } + topPadding: 24 + leftPadding: 24 + Label { + text: "Contact" + textStyle { + base: SystemDefaults.TextStyles.TitleText + color: Color.create("#beaa79") + fontSizeValue: 10 + fontSize: FontSize.PointValue + } + } + TouchContainer { + layout: StackLayout { + orientation: LayoutOrientation.LeftToRight + } + Label { + text: item != null ? myapp.contactMap.Name : '' + textStyle { + base: SystemDefaults.TextStyles.TitleText + color: Color.White + fontSizeValue: 10 + fontSize: FontSize.PointValue + } + } + ImageView { + visible: { + if (item != null && myapp.contactMap.bbmpin__c) true; + else false + } + imageSource: "asset:///images/ShareBBM.png" + } + onTriggered: { + if (myapp.contactMap.bbmpin__c) myapp.launchBBMChat(myapp.contactMap.bbmpin__c); + else if (myapp.contactMap.Phone) dialNumber.initiateCall(myapp.contactMap.Phone); + else myapp.showToast("Unable to reach the contact") + + } + } + } + + attachedObjects: [ + ImagePaintDefinition { + id: woodBG + repeatPattern: RepeatPattern.Fill + imageSource: "asset:///images/wood_background.png" + } + /*, + * Phone { + * id: dialNumber + * }*/ + ] +} diff --git a/SFDC-BrewApp/assets/BarDetailsPage.qml b/SFDC-BrewApp/assets/BarDetailsPage.qml new file mode 100644 index 0000000..1dbf276 --- /dev/null +++ b/SFDC-BrewApp/assets/BarDetailsPage.qml @@ -0,0 +1,286 @@ +/* Copyright (c) 2012 Research In Motion Limited. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + + +import bb.cascades 1.0 +import bb.system 1.0 +import bbmodel 1.0 + +Page { + id: detailPage + property real averageQuantity: 0 + + property variant item: null + property Page mapPage + + function getMapPage() { + if (! mapPage) { + mapPage = mapPageDefinition.createObject(); + } + return mapPage; + } + + onItemChanged: { + if ( item != null ) { +// dataRequest.execute("/services/data/v27.0/query/?q=SELECT+%NAMESPACE%__Product__r.ProductCode,+%NAMESPACE%__quantity_remaining__c,+%NAMESPACE%__quantity__c,+%NAMESPACE%__Product__r.Name+FROM+%NAMESPACE%__Volume__c+WHERE+(%NAMESPACE%__Account__r.AccountNumber+='" + item.__Account__r.AccountNumber + "' and %NAMESPACE%__date__c > LAST_WEEK)") + dataRequest.execute("/services/data/v27.0/query/?q=SELECT+%NAMESPACE%__Product__r.ProductCode,+%NAMESPACE%__quantity_remaining__c,+%NAMESPACE%__quantity__c,+%NAMESPACE%__Product__r.Name+FROM+%NAMESPACE%__Volume__c+WHERE+(%NAMESPACE%__Account__r.AccountNumber+='" + item.__Account__r.AccountNumber + "')") + } + } + + onCreationCompleted: { + } + + titleBar: TitleBar { kind: TitleBarKind.FreeForm + kindProperties: FreeFormTitleBarKindProperties { + content: Container { + background: headerBG.imagePaint + layoutProperties: StackLayoutProperties { + spaceQuota: 1.0 + } + layout: DockLayout { + } + leftPadding: 16 + Label { + verticalAlignment: VerticalAlignment.Center + text: item ? item.__Account__r.Name : '' + textStyle.fontSizeValue: 12 + textStyle.fontSize: FontSize.PointValue + } + } + } + } + Container { + BarDetailsHeaderContainer { + item: detailPage.item + myapp: Qt.app + } + + Container { + layout: DockLayout { + } + horizontalAlignment: HorizontalAlignment.Fill + verticalAlignment: VerticalAlignment.Fill + + Container { + id: indicatorContainer + layout: DockLayout { + } + horizontalAlignment: HorizontalAlignment.Fill + verticalAlignment: VerticalAlignment.Fill + ActivityIndicator { + id: myIndicator + preferredWidth: 100 + horizontalAlignment: HorizontalAlignment.Center + verticalAlignment: VerticalAlignment.Center + running: dataRequest.isLoading + visible: dataRequest.isLoading + } + } + Container { + id: listContainer + ListView { + id: listView + + property real avgQuantity : averageQuantity + horizontalAlignment: HorizontalAlignment.Fill + + dataModel: beerDataModel + + onTriggered: { + if (indexPath == 0) { + var page = detailPageDefinition.createObject(); + var beers = []; + + for (var i = 1; i < beerDataModel.size(); i ++) { + var beer = beerDataModel.value(i)["__Product__r"]; + var queryId = "'" + beer.ProductCode + "'"; + beers.push(queryId); + } + + page.bar = detailPage.item.__Account__r + page.beers = beers.join(","); + navigationPane.push(page); + } else { + var page = detailPageDefinition.createObject(); + var beer = dataModel.value(indexPath)["__Product__r"]; + page.bar = detailPage.item.__Account__r + page.beer = beer; + navigationPane.push(page); + } + } + function itemType(data, indexPath) { + if (indexPath == 0) return "summary"; + return "item"; + } + function getItem() { + return item; + } + function getApp() { + return Qt.app; + } + function getAverage() { + return averageQuantity; + } + function getColorWithBeerPercentage(percentage) { + if (percentage > 0.75) return Color.create("#73a039"); + if (percentage > 0.45) return Color.create("#e5d15c"); + return Color.create("#a53927"); + } + function getAccessoryImageWithBeerPercentage(percentage) { + if (percentage > 0.75) return "asset:///images/ribbon_green.png"; + if (percentage > 0.45) return "asset:///images/ribbon_yellow.png"; + return "asset:///images/ribbon_red.png"; + } + function launchBBM(pin) { + app.launchBBMChat(pin) + } + + listItemComponents: [ + ListItemComponent { + type: "summary" + BreweryItemContainer { + imageView.imageSource: "asset:///images/beer/ic_all.png" + titleLabel.text: "All" + titleLabel.textStyle.fontSizeValue: 11 + accessoryImage: ListItem.view.getAccessoryImageWithBeerPercentage(ListItem.view.getAverage()) + percentageLabel.text: Math.floor(ListItem.view.getAverage() * 100) + "%" + percentageLabel.textStyle.color: ListItem.view.getColorWithBeerPercentage(ListItem.view.getAverage()) + } + }, + ListItemComponent { + type: "item" + + BreweryItemContainer { + property variant beerData: ListItem.data.__Product__r + imageView.imageSource: "asset:///images/beer/" + getImageName() + ".png" + titleLabel.text: beerData.Name + titleLabel.textStyle.fontSizeValue: 11 + accessoryImage: ListItem.view.getAccessoryImageWithBeerPercentage(ListItem.data.__quantity_remaining__c / ListItem.data.__quantity__c) + percentageLabel.text: Math.floor((ListItem.data.__quantity_remaining__c / ListItem.data.__quantity__c) * 100) + "%" + percentageLabel.textStyle.color: ListItem.view.getColorWithBeerPercentage(ListItem.data.__quantity_remaining__c / ListItem.data.__quantity__c) + + function getImageName() { + if (beerData.Name == "Blond Ale") return "blonde-ale"; + else if (beerData.Name == "Brown Ale") return "brown-ale"; + else if (beerData.Name == "Olde Pelican") return "olde-pelican"; + else if (beerData.Name == "Pale Ale") return "pale-ale"; + else if (beerData.Name == "Red Ale") return "red-ale"; + else return "blank.png" + } + } + } + ] + } + } + } + } + + actions: [ + ActionItem { + title: qsTr("Map") + ActionBar.placement: ActionBarPlacement.OnBar + imageSource: "asset:///images/ActionBar_Map.png" + enabled: { + if (item != null && item.__Account__r.__BillingLatitude__c && item.__Account__r.__BillingLongitude__c) { + true; + } else { + false; + } + } + onTriggered: { + var page = getMapPage(); + page.item = item + navigationPane.push(page); + page.setupMap() + } + }, + ActionItem { + title: qsTr("Call") + ActionBar.placement: ActionBarPlacement.OnBar + imageSource: "asset:///images/ActionBar_Call.png" + enabled: { + if (item != null && app.contactMap.Phone) { + true; + } else { + false; + } + } + onTriggered: { + app.launchPhone(app.contactMap.Phone); + } + }, + ActionItem { + title: qsTr("BBM") + ActionBar.placement: ActionBarPlacement.OnBar + imageSource: "asset:///images/ActionBar_BBM.png" + enabled: { + if (item != null && app.contactMap.__bbmpin__c) { + true; + } else { + false; + } + } + onTriggered: { + app.launchBBMChat(app.contactMap.__bbmpin__c) + } + } + ] + paneProperties: NavigationPaneProperties { + backButton: ActionItem { + onTriggered: { + navigationPane.pop() + } + } + } + + attachedObjects: [ + ImagePaintDefinition { + id: headerBG + repeatPattern: RepeatPattern.Fill + imageSource: "asset:///images/header_blank.png" + }, + ComponentDefinition { + id: detailPageDefinition + source: "BeerStatsPage.qml" + }, + ComponentDefinition { + id: mapPageDefinition + source: "BarMap.qml" + }, + /*Phone { + id: dialNumber + },*/ + DataRequest { + id: dataRequest + + onAverageCalculated: { + averageQuantity = result; + } + onDataReturned: { + var dataList = result["records"]; + beerDataModel.clear() + beerDataModel.append({itemType: "summary (AKA *All* button, this is the first record in the list..... we just need a blank record so the list has 1 additional row.............)"}); + beerDataModel.append(dataList); + } + }, + ArrayDataModel { + id: beerDataModel + }, + Header{ + id: summaryObj + objectName: "summary" + } + ] +} diff --git a/SFDC-BrewApp/assets/BarMap.qml b/SFDC-BrewApp/assets/BarMap.qml new file mode 100644 index 0000000..8683085 --- /dev/null +++ b/SFDC-BrewApp/assets/BarMap.qml @@ -0,0 +1,94 @@ +/* Copyright (c) 2012 Research In Motion Limited. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + + +import bb.cascades 1.0 +import bb.cascades.maps 1.0 +import bbmodel 1.0 + +Page { + property variant item: {} + property Page detailPage + + function getDetailPage() { + if (! detailPage) { + detailPage = detailPageDefinition.createObject(); + } + return detailPage; + } + titleBar: TitleBar {kind: TitleBarKind.FreeForm + kindProperties: FreeFormTitleBarKindProperties { + content : Container { + background: headerBG.imagePaint + layoutProperties: StackLayoutProperties { + spaceQuota: 1.0 + } + layout: DockLayout { + } + leftPadding: 16 + Label { + verticalAlignment: VerticalAlignment.Center + text: item.Name + textStyle.fontSizeValue: 12 + textStyle.fontSize: FontSize.PointValue + } + } + } + } + Container { + MapView { + id: mapView + horizontalAlignment: HorizontalAlignment.Fill + preferredWidth: 768 + preferredHeight: 1100 + altitude: 2000 + latitude : 28.408615 + longitude : -81.586189 + + onCreationCompleted:{ + mapView.captionButtonClicked.connect(invokeGo) + mapView.captionLabelTapped.connect(popPage) + } + } + } + + function popPage() { + navigationPane.pop() + } + + function invokeGo(){ + routeInvokerID.endLatitude = item.__Account__r.__BillingLatitude__c; // item["__BillingLatitude__c"] + routeInvokerID.endLongitude = item.__Account__r.__BillingLongitude__c // item["__BillingLongitude__c"] + routeInvokerID.endName = item.__Account__r.Name // item["Name"] + routeInvokerID.endDescription = (item.__Account__r.__Address__c + ", " + item.__Account__r.__City__c ) // (item["__address__c"] + ", " + item["__city__c"]) + routeInvokerID.go(); + } + + function setupMap(){ + // app.showMap(mapView, item["__BillingLatitude__c"], item["__BillingLongitude__c"], item["Name"], (item["__address__c"] + ", " + item["__city__c"])) + app.showMap(mapView, item.__Account__r.__BillingLatitude__c, item.__Account__r.__BillingLongitude__c,item.__Account__r.Name, (item.__Account__r.__Address__c + ", " + item.__Account__r.__City__c )); + } + + attachedObjects: [ + ImagePaintDefinition { + id: headerBG + repeatPattern: RepeatPattern.Fill + imageSource: "asset:///images/header_blank.png" + }, + RouteMapInvoker { + id: routeInvokerID + } + ] +} diff --git a/SFDC-BrewApp/assets/BarsListView.qml b/SFDC-BrewApp/assets/BarsListView.qml new file mode 100644 index 0000000..3ddc08a --- /dev/null +++ b/SFDC-BrewApp/assets/BarsListView.qml @@ -0,0 +1,102 @@ +/* Copyright (c) 2012 Research In Motion Limited. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + + +import bb.cascades 1.0 + +Container { + property variant navigationPane + + function loadDetailsPage(barId) { + console.log("loading details (" + app.dataModel.size() + ") page for bar id " + barId); + + for (var i = 0; i < app.dataModel.size(); i ++) { + var item = app.dataModel.data([ i ]); + if (item.__Account__r.AccountNumber == barId) { + var page = detailDefinition.createObject(); + page.item = item.__Account__r; + + while ( navigationPane.count() > 1 ) { + var pane = navigationPane.at(navigationPane.count() - 1); + navigationPane.remove(pane); + pane.destroy(); + } + + navigationPane.push(page); + } + } + } + onCreationCompleted: { + app.gotoDetails.connect(loadDetailsPage); + console.log("[BarsListView] Creating Bars List View"); + } + + RefreshableListView { + id: listView + horizontalAlignment: HorizontalAlignment.Fill + + dataModel: app.dataModel + + onTriggered: { + //indexPath + console.log("BarsListView::onTriggered") + var page = detailDefinition.createObject(); + var item = dataModel.data(indexPath); + page.item = item; + app.requestContact(page.item.__Account__r.AccountNumber) + navigationPane.push(page); + + } + + function itemType(data, indexPath) { + return "item"; + } + + onUpdateTriggered: { + console.log("BarsListView::onUpdateTriggered") + app.refreshBars(); + } + + onCreationCompleted: { + app.dataModelChanged.connect(listView.updateFinished) + } + + function getAccessoryImageWithBeerState(state) { + if (state == 1) return "asset:///images/ribbon_red.png"; + if (state == 2) return "asset:///images/ribbon_yellow.png"; + return "asset:///images/ribbon_green.png"; + } + + listItemComponents: [ + ListItemComponent { + type: "item" + BreweryItemContainer { + property variant barData : ListItem.data.__Account__r + titleLabel.text: barData.Name + subtitleLabel.text: barData.__Address__c + accessoryImage: ListItem.view.getAccessoryImageWithBeerState(barData.__beer_state__c) + imageView.imageSource: barData.AccountNumber ? "asset:///images/bars/" + barData.AccountNumber + ".png" : "" + } + } + ] + } + + attachedObjects: [ + ComponentDefinition { + id: detailDefinition + source: "asset:///BarDetailsPage.qml" + } + ] +} \ No newline at end of file diff --git a/SFDC-BrewApp/assets/BarsMapView.qml b/SFDC-BrewApp/assets/BarsMapView.qml new file mode 100644 index 0000000..7083797 --- /dev/null +++ b/SFDC-BrewApp/assets/BarsMapView.qml @@ -0,0 +1,73 @@ +/* Copyright (c) 2012 Research In Motion Limited. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + + +import bb.cascades 1.0 +import bb.cascades.maps 1.0 +import bb.platform 1.0 +import bbmodel 1.0 + +Container { + property variant navigationPane + + horizontalAlignment: HorizontalAlignment.Fill + verticalAlignment: VerticalAlignment.Fill + background: Color.Red + + MapView { + id: barMapView + objectName: "barMapView" + horizontalAlignment: HorizontalAlignment.Fill + preferredWidth: 768 + } + + onCreationCompleted: { + barMapView.captionButtonClicked.connect(mapGoInvoker) + barMapView.captionLabelTapped.connect(mapCaptionLabelTapped) + app.dataModelChanged.connect(setupMapPins) + } + + function setupMapPins(){ + app.showMapAll(barMapView) + } + + function mapCaptionLabelTapped(){ + var page = detailDefinition.createObject(); + var item = app.dataModel.value(barMapView.focusedId); + item.id = barMapView.focusedId; + page.item = item; + navigationPane.push(page); + } + + function mapGoInvoker(){ + var item1 = app.dataModel.value(barMapView.focusedId); + var item = item1["__Account__r"] + routeInvokerID.endLatitude = item["__BillingLatitude__c"] + routeInvokerID.endLongitude = item["__BillingLongitude__c"] + routeInvokerID.endName = item["Name"] + routeInvokerID.endDescription = (item["__Address__c"] + ", " + item["__City__c"]) + routeInvokerID.go(); + } + + attachedObjects: [ + RouteMapInvoker { + id: routeInvokerID + }, + ComponentDefinition { + id: detailDefinition + source: "BarDetailsPage.qml" + } + ] +} diff --git a/SFDC-BrewApp/assets/BeerStatsPage.qml b/SFDC-BrewApp/assets/BeerStatsPage.qml new file mode 100644 index 0000000..4a108c3 --- /dev/null +++ b/SFDC-BrewApp/assets/BeerStatsPage.qml @@ -0,0 +1,287 @@ +/* Copyright (c) 2012 Research In Motion Limited. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + + +import bb.cascades 1.0 +import bbmodel 1.0 + +Page { + + property variant beer: null + property variant bar: null + property variant beers: null + property variant grouping: 0 // week + + property variant data: { + "Jan": 45, + "Feb": 23, + "Mar": 56, + "Apr": 55, + "May": 45 + } + + property VolumeRequest volumeRequest: VolumeRequest { + id: volumeRequest + barid: bar.AccountNumber + + onSuccess: { + data = records; + chartWebView.postMessage(chartWebView.message(chartWebView.bridgeCommandShowData, JSON.stringify(data))); + contentContainer.visible = true; + } + } + + onBeerChanged: { + if ( beer != null ) { + console.log("BEER ID = " + beer.ProductCode) + + console.log("AccountNumber ID = " + bar.AccountNumber) + + volumeRequest.beerid = beer.ProductCode; + volumeRequest.execute(); + } + } + onBeersChanged: { + if (beers != null) { + volumeRequest.beerids = beers; + volumeRequest.execute(); + } + } + + titleBar: TitleBar { + kind: TitleBarKind.FreeForm + kindProperties: FreeFormTitleBarKindProperties { + content: Container { + background: headerBG.imagePaint + layoutProperties: StackLayoutProperties {spaceQuota: 1.0} + layout: DockLayout {} + leftPadding: 16 + Label { + verticalAlignment: VerticalAlignment.Center + text: beer != null ? beer.Name : "All" + textStyle.fontSizeValue: 12 + textStyle.fontSize: FontSize.PointValue + } + ActivityIndicator { + id: activityIndicator + preferredWidth: 100 + horizontalAlignment: HorizontalAlignment.Right + verticalAlignment: VerticalAlignment.Center + running: volumeRequest.isLoading + visible: volumeRequest.isLoading + } + } + } + } + +ScrollView{ + ScrollView { + horizontalAlignment: HorizontalAlignment.Fill + verticalAlignment: VerticalAlignment.Fill + + Container { + Container { + horizontalAlignment: HorizontalAlignment.Fill + topPadding: 22 + + //background: Color.Green + background: graphBG.imagePaint + preferredHeight: 696 + minHeight: 696 + + DropDown { + enabled: false + horizontalAlignment: HorizontalAlignment.Center + options: [ + Option { + text: "Week" + selected: true + }, + Option { + text: "Month" + }, + Option {text: "Year"} + ] + } + + Container { + id: contentContainer + topPadding: 2 + horizontalAlignment: HorizontalAlignment.Center + verticalAlignment: VerticalAlignment.Center + preferredWidth: 720 + preferredHeight: 500 + background: Color.Black + visible: false + + layout: DockLayout {} + + WebView { + property variant bridgeStateInitializing: 0 + property variant bridgeStateReady: 1 + property variant bridgeState: 0 + + property variant bridgeCommandBridgeState: "BridgeCommandBridgeState" // in + property variant bridgeCommandDebug: "BridgeCommandDebug" // in + property variant bridgeCommandShowData: "BridgeCommandShowData" // out + + implicitLayoutAnimationsEnabled: false + objectName: "chartWebView" + id: chartWebView + url: "local:///assets/chart/index.html" + + verticalAlignment: VerticalAlignment.Fill + horizontalAlignment: HorizontalAlignment.Fill + + onMessageReceived: { + var components = message.data.split('::'); + var cmd = components[0]; + var value = components[1]; + if (cmd == bridgeCommandBridgeState) { + bridgeState = value; + if (bridgeState == bridgeStateReady) { + console.log('[JavasriptDebug] Bridge Confirmed Ready by JS'); + //chartWebView.postMessage(chartWebView.message(bridgeCommandShowData, JSON.stringify(data))); + } + } else if (cmd == bridgeCommandDebug) { + console.log("[JavasriptMessageDebug] " + value); + } + } + + function message(cmd, value) { + return cmd + "::" + value; + } + } + Container { + verticalAlignment: VerticalAlignment.Top + horizontalAlignment: HorizontalAlignment.Fill + layout: DockLayout {} + preferredHeight: 85 + + TouchContainer { + horizontalAlignment: HorizontalAlignment.Left + preferredHeight: 85 + preferredWidth: 85 + enabled: volumeRequest.offset > 0 + + ImageView { + imageSource: "asset:///images/previous.png" + } + onTriggered: { + volumeRequest.offset = Math.max(volumeRequest.offset - 1, 0); + volumeRequest.execute(); + } + } + TouchContainer { + horizontalAlignment: HorizontalAlignment.Right + preferredHeight: 85 + preferredWidth: 85 + layout: DockLayout {} + + ImageView { + horizontalAlignment: HorizontalAlignment.Right + imageSource: "asset:///images/next.png" + } + onTriggered: { + volumeRequest.offset = volumeRequest.offset + 1; + volumeRequest.execute(); + } + } + } + } + + } + Container { + horizontalAlignment: HorizontalAlignment.Fill + verticalAlignment: VerticalAlignment.Fill + background: woodBG.imagePaint + + preferredHeight: 344 + + Container { + horizontalAlignment: HorizontalAlignment.Center + layout: StackLayout { + orientation: LayoutOrientation.LeftToRight + } + Container { + layoutProperties: StackLayoutProperties { + spaceQuota: 0.65 + } + Label { + text: "Least Quantity" + textStyle.textAlign: TextAlign.Right + horizontalAlignment: HorizontalAlignment.Fill + textStyle.fontSize: FontSize.PointValue + textStyle.fontSizeValue: 12 + } + Label { + text: "Most Quantity" + textStyle.textAlign: TextAlign.Right + horizontalAlignment: HorizontalAlignment.Fill + textStyle.fontSize: FontSize.PointValue + textStyle.fontSizeValue: 12 + } + Label { + text: "Average Quantity" + textStyle.textAlign: TextAlign.Right + horizontalAlignment: HorizontalAlignment.Fill + textStyle.fontSize: FontSize.PointValue + textStyle.fontSizeValue: 12 + } + } + Container { + leftPadding: 18 + layoutProperties: StackLayoutProperties { + spaceQuota: 0.35 + } + Label { + text: volumeRequest.min + textStyle.fontSize: FontSize.PointValue + textStyle.fontSizeValue: 12 + } + Label { + text: volumeRequest.max + textStyle.fontSize: FontSize.PointValue + textStyle.fontSizeValue: 12 + } + Label { + text: volumeRequest.average + textStyle.fontSize: FontSize.PointValue + textStyle.fontSizeValue: 12 + } + } + } + } + } + } +} + attachedObjects: [ + ImagePaintDefinition { + id: headerBG + repeatPattern: RepeatPattern.Fill + imageSource: "asset:///images/header_blank.png" + }, + ImagePaintDefinition { + id: woodBG + repeatPattern: RepeatPattern.Fill + imageSource: "asset:///images/wood_background.png" + }, + ImagePaintDefinition { + id: graphBG + repeatPattern: RepeatPattern.Fill + imageSource: "asset:///images/graph_background.png" + } + ] +} diff --git a/SFDC-BrewApp/assets/BreweryItemContainer.qml b/SFDC-BrewApp/assets/BreweryItemContainer.qml new file mode 100644 index 0000000..058a134 --- /dev/null +++ b/SFDC-BrewApp/assets/BreweryItemContainer.qml @@ -0,0 +1,131 @@ +/* Copyright (c) 2012 Research In Motion Limited. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + + +import bb.cascades 1.0 + +Container { + property real height: 112.0 + property alias titleLabel: itemTitleLabel + property alias subtitleLabel: itemSubtitleLabel + property alias accessoryImage: accessoryImageView.imageSource + property alias percentageLabel: percentageLabel + property alias imageView: imageView + + id: itemContainer + preferredHeight: height + + ImageView { + imageSource: "asset:///images/divider.png" + maxHeight: 2 + minHeight: 2 + preferredHeight: 2 + + layoutProperties: StackLayoutProperties { + spaceQuota: 1.0 + } + } + + Container { + layout: StackLayout { + orientation: LayoutOrientation.LeftToRight + } + + preferredHeight: height - 2 + + ImageView { + id: imageView + maxWidth: 155.0 + preferredHeight: height - 3 + } + Container { + layoutProperties: StackLayoutProperties { + spaceQuota: 1.0 + } + + layout: DockLayout { + + } + verticalAlignment: VerticalAlignment.Center + + Container { + layout: AbsoluteLayout { + } + Label { + id: itemTitleLabel + textStyle.color: Color.White + textStyle.fontSizeValue: 9 + textStyle.fontSize: FontSize.PointValue + layoutProperties: AbsoluteLayoutProperties { + positionX: 3 + } + } + Label { + id: itemSubtitleLabel + visible: itemSubtitleLabel.text != null + textStyle.color: Color.create("#717854") + textStyle.fontSizeValue: 7 + textStyle.fontSize: FontSize.PointValue + layoutProperties: AbsoluteLayoutProperties { + positionY: 46 + positionX: 3 + } + } + } + } + Container { + maxHeight: 111.0 + minHeight: 111.0 + preferredHeight: 111.0 + layout: DockLayout { + } + Label { + id: percentageLabel + verticalAlignment: VerticalAlignment.Center + } + } + Container { + maxHeight: 111.0 + minHeight: 111.0 + preferredHeight: 111.0 + layout: DockLayout {} + ImageView { + id: accessoryImageView + //imageSource: + verticalAlignment: VerticalAlignment.Center + + } + } + + } + + function setHighlight(highlighted) { + if (highlighted) { + itemContainer.background = Color.create("#454b32"); + } else { + itemContainer.background = Color.Transparent; + } + } + + // Connect the onActivedChanged signal to the highlight function + ListItem.onActivationChanged: { + setHighlight(ListItem.active); + } + + // Connect the onSelectedChanged signal to the highlight function + ListItem.onSelectionChanged: { + setHighlight(ListItem.selected); + } +} \ No newline at end of file diff --git a/SFDC-BrewApp/assets/LoginPage.qml b/SFDC-BrewApp/assets/LoginPage.qml new file mode 100644 index 0000000..1372c1d --- /dev/null +++ b/SFDC-BrewApp/assets/LoginPage.qml @@ -0,0 +1,120 @@ +/* Copyright (c) 2012 Research In Motion Limited. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + + +import bb.cascades 1.0 +import bbmodel 1.0 + +Page { + signal login + property int timeout: 30000 + property variant indicatorVisibility: activityIndicator.visible + property variant indicatorRunning: activityIndicator.running + property ActivityIndicator indicator: activityIndicator + property int deviceW: uiConstants.deviceW + property int deviceH: uiConstants.deviceH + property WebView myWebView : theWebview + id: thePage + + content: Container { + layout: AbsoluteLayout { + } + + background: Color.White + + ScrollView { + id: scrollview + horizontalAlignment: HorizontalAlignment.Center + verticalAlignment: VerticalAlignment.Center + scrollViewProperties { + pinchToZoomEnabled: false + scrollMode: ScrollMode.Vertical + } + preferredHeight: deviceH + layoutProperties: StackLayoutProperties { + spaceQuota: 1.0 + } + Container { + id: webview + objectName: "webview" + WebView { + id: theWebview + objectName: "theWebview" + url: "https://login.salesforce.com/services/oauth2/authorize?response_type=token&display=touch&client_id=3MVG9A2kN3Bn17hsPYpFs3rM41gU2sM1ECpdJ.gaLZA8ti9nJ4980zNiDpAfrx63c5t_9H_icxsLKKwCx_gQZ&redirect_uri=sfdc://success" + visible: false + onCreationCompleted: { + app.loggedIn.connect(login) + } + onLoadingChanged: { + if (loadRequest.status == WebLoadStatus.Started) { + activityIndicator.visible = true; + activityIndicator.running = true; + timer.start(timeout); + } else if (loadRequest.status == WebLoadStatus.Succeeded) { + activityIndicator.visible = false; + activityIndicator.running = false; + timer.stop(); + + theWebview.evaluateJavaScript("document.getElementById('username').value = 'waasample@gmail.com';"); + theWebview.evaluateJavaScript("document.getElementById('password').value = 'blackberry10';"); + + theWebview.visible = true + } else if (loadRequest.status == WebLoadStatus.Failed) { + activityIndicator.visible = false; + activityIndicator.running = false; + timer.stop(); + theWebview.visible = true + } + } + onLoadProgressChanged: { + timer.start(timeout); + activityIndicator.visible = true; + activityIndicator.running = true; + } + onNavigationRequested: { + app.navigationRequested(request.url) + } + attachedObjects: [ + QTimer { + id: timer + interval: timeout + singleShot: true + onTimeout: { + app.showToast("Request timed out. Please try again later.") + theWebview.stop(); + activityIndicator.visible = false; + activityIndicator.running = false; + } + } + ] + } + } + } + ActivityIndicator { + id: activityIndicator + preferredHeight: 100 + preferredWidth: 100 + layoutProperties: AbsoluteLayoutProperties { + positionX: deviceW / 2 - 50 + positionY: (deviceH) / 2 - 50 // 110 is title bar height - 140 is nav bar height ... 50 is our height/2... + } + horizontalAlignment: HorizontalAlignment.Center + verticalAlignment: VerticalAlignment.Center + + visible: false + running: false + } + } +} \ No newline at end of file diff --git a/SFDC-BrewApp/assets/MainPage.qml b/SFDC-BrewApp/assets/MainPage.qml new file mode 100644 index 0000000..7ad7eda --- /dev/null +++ b/SFDC-BrewApp/assets/MainPage.qml @@ -0,0 +1,118 @@ +/* Copyright (c) 2012 Research In Motion Limited. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + + +// Navigation pane project template +import bb.cascades 1.0 + +Page { + id: transitionPage + property Container barsListView + property Container barsMapView + + //function pushPage + function getBarsView() { + if (barsListView == null) { + barsListView = barsDefinition.createObject(); + barsListView.navigationPane = navigationPane; + contentView.add(barsListView); + } + return barsListView; + } + function showBarsView() { + getMapsView().visible = false; + getBarsView().visible = true; + } + + function getMapsView() { + if ( barsMapView == null ) { + barsMapView = mapDefinition.createObject(); + barsMapView.navigationPane = navigationPane; + contentView.add(barsMapView); + } + + return barsMapView; + } + function showMapsView() { + getBarsView().visible = false; + getMapsView().visible = true; + } + + titleBar: TitleBar { + kind: TitleBarKind.FreeForm + kindProperties: FreeFormTitleBarKindProperties { + content: Container { + ImageView { + imageSource: "asset:///images/header.png" + } + } + } + } + + Container {/* + Container { + ImageView { + imageSource: "asset:///images/header.png" + } + }*/ + SegmentedControl { + options: [ + Option { + text: "Bars" + }, + Option { + text: "Map" + } + ] + onSelectedIndexChanged: { + //selectedIndex + if ( selectedIndex == 0 ) { + transitionPage.showBarsView(); + } else { + transitionPage.showMapsView(); + } + } + } + + background: Color.Black + + Container { + id: contentView + + layout: StackLayout { + orientation: LayoutOrientation.LeftToRight + } + } + + } + onCreationCompleted: { + OrientationSupport.supportedDisplayOrientation = SupportedDisplayOrientation.DisplayPortrait; + + transitionPage.showBarsView(); + transitionPage.getMapsView(); + } + + attachedObjects: [ + ComponentDefinition { + id: barsDefinition + source: "BarsListView.qml" + }, + ComponentDefinition { + id: mapDefinition + source: "BarsMapView.qml" + } + ] +} + diff --git a/SFDC-BrewApp/assets/PullToRefresh.qml b/SFDC-BrewApp/assets/PullToRefresh.qml new file mode 100644 index 0000000..f9c731c --- /dev/null +++ b/SFDC-BrewApp/assets/PullToRefresh.qml @@ -0,0 +1,173 @@ +/* Copyright (c) 2012 Research In Motion Limited. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + + +import bb.cascades 1.0 + +Container { + id: refresh + objectName: "pullToRefresh" + + layout: DockLayout { + } + + signal update + + preferredHeight: 150 + preferredWidth: 768 + + property int y : 0 + property bool updating : false + property bool shouldAnimateReset : true + + property variant list + + Container { + layout: StackLayout { + orientation: LayoutOrientation.LeftToRight + } + verticalAlignment: VerticalAlignment.Center + horizontalAlignment: HorizontalAlignment.Left + + leftPadding: 40 + + ActivityIndicator { + id: activityIndicator + preferredWidth: 50 + preferredHeight: 50 + visible: activityIndicator.running + } + + ImageView { + id: arrow + imageSource: "asset:///images/pullToRefreshArrow.png" + preferredWidth: 50 + preferredHeight: 50 + rotationZ: 180 + } + + Container { + layout : DockLayout { + + } + + leftMargin: 20 + verticalAlignment: VerticalAlignment.Center + + Label { + id:loading + textStyle.color: Color.create("#f5f5f5") + text: qsTr("Loading...") + verticalAlignment: VerticalAlignment.Center + opacity: 0.0 + textStyle { + fontSize: FontSize.PointValue + fontSizeValue: 7 + fontWeight: FontWeight.W500 + } + attachedObjects: [ + ImplicitAnimationController { + propertyName: "opacity" + enabled: false + } + ] + } + + Label { + id: pull + textStyle.color: Color.create("#f5f5f5") + text: qsTr("Pull down to update", "Text in pull-to-refresh view to update stores") + verticalAlignment: VerticalAlignment.Center + textStyle { + fontSize: FontSize.PointValue + fontSizeValue: 7 + fontWeight: FontWeight.W500 + } + attachedObjects: [ + ImplicitAnimationController { + propertyName: "opacity" + enabled: false + } + ] + } + Label { + id: release + textStyle.color: Color.create("#f5f5f5") + text: qsTr("Release to update", "Text in pull-to-refresh view to update stories") + verticalAlignment: VerticalAlignment.Center + textStyle { + fontSize: FontSize.PointValue + fontSizeValue: 7 + fontWeight: FontWeight.W500 + } + attachedObjects: [ + ImplicitAnimationController { + propertyName: "opacity" + enabled: false + } + ] + } + } + } + + function onListTouchEvent(event) { + if (arrow.visible && event.touchType == TouchType.Up && refresh.y > 0) { + pull.opacity = 0.0; + release.opacity = 0.0 + loading.opacity = 1.0 + arrow.visible = false; + activityIndicator.start(); + refresh.update(); + } + } + + function reset() { + y = -150; + pull.opacity = 1.0; + release.opacity = 0.0 + loading.opacity = 0.0 + arrow.visible = true; + arrow.rotationZ = 180; + activityIndicator.stop(); + + var animate = refresh.shouldAnimateReset; + refresh.shouldAnimateReset = true; + + return animate; + } + + attachedObjects: [ + LayoutUpdateHandler { + onLayoutFrameChanged: { + refresh.y = layoutFrame.y; + + if (!arrow.visible) { + if (layoutFrame.y < 0) refresh.shouldAnimateReset = false; + return; + } + + if (layoutFrame.y <= 0) { + arrow.rotationZ = 180; + pull.opacity = 1.0; + release.opacity = 0.0 + } else { + arrow.rotationZ = 360; + pull.opacity = 0.0; + release.opacity = 1.0; + } + } + } + ] +} diff --git a/SFDC-BrewApp/assets/RefreshableListView.qml b/SFDC-BrewApp/assets/RefreshableListView.qml new file mode 100644 index 0000000..9362f6d --- /dev/null +++ b/SFDC-BrewApp/assets/RefreshableListView.qml @@ -0,0 +1,48 @@ +/* Copyright (c) 2012 Research In Motion Limited. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + + +import bb.cascades 1.0 + +ListView { + id: listView + + signal updateTriggered + + property alias y : refresh.y + property alias listObjectName: listView.objectName + + objectName: "refreshableListView" + + leadingVisual: PullToRefresh { + id: refresh + + onUpdate: { + listView.updateTriggered(); + } + } + + onTouch: { + refresh.onListTouchEvent(event); + } + + function updateFinished() { + var shouldAnimate = refresh.reset(); + + if (shouldAnimate) { + listView.scrollToPosition(ScrollPosition.Beginning, ScrollAnimation.Default); + } + } +} diff --git a/SFDC-BrewApp/assets/TouchContainer.qml b/SFDC-BrewApp/assets/TouchContainer.qml new file mode 100644 index 0000000..25b7d43 --- /dev/null +++ b/SFDC-BrewApp/assets/TouchContainer.qml @@ -0,0 +1,81 @@ +/* Copyright (c) 2012 Research In Motion Limited. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + + +import bb.cascades 1.0 + +Container { + id: self + + // state + property bool isDown: false + property bool beganWithPress: false + + signal triggered; + + onTouch: { + if ( event.isDown() ) { + self.isDown = true; + self.beganWithPress = true; + } else if ( event.isUp() ) { + self.isDown = false; + self.triggered(); + } + } + onTouchEnter: { +// if ( self.beganWithPress == true ) { +// self.isDown = true; +// } + } + onTouchExit: { + self.isDown = false; + } + + onIsDownChanged: { + if ( self.isDown == true ) { + self.scaleDown(); + } else { + self.translationX = 0; + self.translationY = 0; + self.scaleUp(); + } + } + + animations: [ + ScaleTransition { + id: scalePressDown + fromX: 1.0 + toX: 0.95 + fromY: 1.0 + toY: 0.95 + duration: 125 + }, + ScaleTransition { + id: scalePressUp + fromX: 0.95 + toX: 1.0 + fromY: 0.95 + toY: 1.0 + duration: 125 + } + ] + + function scaleDown() { + scalePressDown.play(); + } + function scaleUp() { + scalePressUp.play(); + } +} \ No newline at end of file diff --git a/SFDC-BrewApp/assets/chart/elycharts.js b/SFDC-BrewApp/assets/chart/elycharts.js new file mode 100755 index 0000000..1281b4a --- /dev/null +++ b/SFDC-BrewApp/assets/chart/elycharts.js @@ -0,0 +1,3768 @@ +/********* Source File: src/elycharts_defaults.js*********/ +/*!********************************************************************* + * ELYCHARTS v2.1.4-SNAPSHOT $Id: elycharts.js 52 2011-08-07 19:57:09Z stefano.bagnara@gmail.com $ + * A Javascript library to generate interactive charts with vectorial graphics. + * + * Copyright (c) 2010 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 � quindi data da tutti i valori della conf di default alla quale viene unita (con sovrascrittura) la conf corrente + // Il parametro � ricorsivo (la configurazione di default puo' a sua volta avere una configurazione di default) + // Se non specificato, la configurazione di default � quella con lo stesso nome del tipo di grafico + // template : 'NOME', + + /* DATI: + // I valori associati a ogni serie del grafico. Ogni serie � associata a una chiave dell'oggetto value, il cui + // valore � 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 : [], + */ + + // 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 � 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 � 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' + }, + pixelWorkAround : { + active : true + }, + 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� vicino al punto di inizio)] ] + line : [ [ 0, 0 ], [0, 0] ], + // Propriet� 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, dotR : 4, + dotProps : { type : "rect", width : 10, height : 10 }, + textProps : { font: '12px Arial', fill: "#000" } + }, + debug : { + active : false + } + }, + + nop : 0 + }, + + line : { + template : 'common', + + barMargins : 0, + + // 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� 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� 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 + // 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, + + 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 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) { + // TODO Bisogna chiamare il destroy delle feature? + $env.paper.clear(); + this.html(""); + this.data('elycharts_env', false); + } + } + } + else 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); + + _processGenericConfig($env, $options); + $env.pieces = $.elycharts[$env.opt.type].draw($env); + + this.data('elycharts_env', $env); + + } else { + $options = _normalizeOptions($options, $env.opt); + + // Already initialized + $env.oldopt = common._clone($env.opt); + $env.opt = $.extend(true, $env.opt, $options); + $env.newopt = $options; + + _processGenericConfig($env, $options); + $env.pieces = $.elycharts[$env.opt.type].draw($env); + } + + return this; +} + +/** + * Must be called only in first call to .chart, to initialize elycharts environment. + */ +function _initEnv($container, $options) { + if (!$options.width) + $options.width = $container.width(); + if (!$options.height) + $options.height = $container.height(); + + var $env = { + id : $.elycharts.lastId ++, + paper : common._RaphaelInstance($container.get()[0], $options.width, $options.height), + 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); +} + +/** + * 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 cicle + 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 deftype = $fullopt.type != 'line' ? $fullopt.type : ($options.defaultSeries.type ? $options.defaultSeries.type : ($fullopt.defaultSeries.type ? $fullopt.defaultSeries.type : 'line')); + _normalizeOptionsColor($options.defaultSeries, deftype, $fullopt); + if ($options.defaultSeries.stackedWith) { + $options.defaultSeries.stacked = $options.defaultSeries.stackedWith; + delete $options.defaultSeries.stackedWith; + } + } + + if ($options.series) + for (var serie in $options.series) { + var type = $fullopt.type != 'line' ? $fullopt.type : ($options.series[serie].type ? $options.series[serie].type : ($fullopt.series[serie].type ? $fullopt.series[serie].type : (deftype ? deftype : 'line'))); + _normalizeOptionsColor($options.series[serie], type, $fullopt); + if ($options.series[serie].values) + for (var value in $options.series[serie].values) + _normalizeOptionsColor($options.series[serie].values[value], type, $fullopt); + + if ($options.series[serie].stackedWith) { + $options.series[serie].stacked = $options.series[serie].stackedWith; + delete $options.series[serie].stackedWith; + } + } + + 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. +* @param $section Section part of external conf passed +* @param $type Type of plot (for line chart can be "line" or "bar", for other types is equal to chart type) +*/ +function _normalizeOptionsColor($section, $type, $fullopt) { + if ($section.color) { + var color = $section.color; + + if (!$section.plotProps) + $section.plotProps = {}; + + if ($type == 'line') { + if ($section.plotProps && !$section.plotProps.stroke && !$fullopt.defaultSeries.plotProps.stroke) + $section.plotProps.stroke = color; + } else { + if ($section.plotProps && !$section.plotProps.fill && !$fullopt.defaultSeries.plotProps.fill) + $section.plotProps.fill = color; + } + + if (!$section.tooltip) + $section.tooltip = {}; + // Is disabled in defaultSetting i should not set color + if (!$section.tooltip.frameProps && $fullopt.defaultSeries.tooltip.frameProps) + $section.tooltip.frameProps = {}; + if ($section.tooltip && $section.tooltip.frameProps && !$section.tooltip.frameProps.stroke && !$fullopt.defaultSeries.tooltip.frameProps.stroke) + $section.tooltip.frameProps.stroke = color; + + if (!$section.legend) + $section.legend = {}; + if (!$section.legend.dotProps) + $section.legend.dotProps = {}; + if ($section.legend.dotProps && !$section.legend.dotProps.fill) + $section.legend.dotProps.fill = color; + + if ($type == 'line') { + if (!$section.dotProps) + $section.dotProps = {}; + if ($section.dotProps && !$section.dotProps.fill && !$fullopt.defaultSeries.dotProps.fill) + $section.dotProps.fill = color; + + if (!$section.fillProps) + $section.fillProps = {}; + if ($section.fillProps && !$section.fillProps.fill && !$fullopt.defaultSeries.fillProps.fill) + $section.fillProps.fill = color; + } + } +} + +/*********************************************************************** + * 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; + }, + + _mergeObjects : function(o1, o2) { + return $.extend(true, o1, o2); + /* + if (typeof o1 == 'undefined') + return o2; + if (typeof o2 == 'undefined') + return o1; + + for (var idx in o2) + if (typeof o1[idx] == 'undefined') + o1[idx] = this._clone(o2[idx]); + else if (typeof o2[idx] == 'object') { + if (typeof o1[idx] == 'object') + o1[idx] = this._mergeObjects(o1[idx], o2[idx]); + else + o1[idx] = this._mergeObjects({}, o2[idx]); + } + else + o1[idx] = this._clone(o2[idx]); + return o1;*/ + }, + + 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].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; + }, + + /** + * 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; + + // TODO fare una cache e fix del toLowerCase (devono solo fare la prima lettera + if (!subsection) { + if (typeof serie == 'undefined' || !serie) + props = env.opt[section.toLowerCase()]; + + else { + props = this._clone(env.opt['default' + section]); + if (env.opt[section .toLowerCase()] && env.opt[section.toLowerCase()][serie]) + props = this._mergeObjects(props, env.opt[section.toLowerCase()][serie]); + + if ((typeof index != 'undefined') && index >= 0 && props['values'] && props['values'][index]) + props = this._mergeObjects(props, props['values'][index]); + } + + } else { + props = this._clone(env.opt[subsection.toLowerCase()]); + + if (typeof serie == 'undefined' || !serie) { + if (env.opt[section.toLowerCase()] && env.opt[section.toLowerCase()][subsection.toLowerCase()]) + props = this._mergeObjects(props, env.opt[section.toLowerCase()][subsection.toLowerCase()]); + + } else { + if (env.opt['default' + section] && env.opt['default' + section][subsection.toLowerCase()]) + props = this._mergeObjects(props, env.opt['default' + section][subsection.toLowerCase()]); + + if (env.opt[section .toLowerCase()] && env.opt[section.toLowerCase()][serie] && env.opt[section.toLowerCase()][serie][subsection.toLowerCase()]) + props = this._mergeObjects(props, env.opt[section.toLowerCase()][serie][subsection.toLowerCase()]); + + if (props && (typeof index != 'undefined') && index > 0 && props['values'] && props['values'][index]) + props = this._mergeObjects(props, props['values'][index]); + } + } + + return props; + }, + + absrectpath : function(x1, y1, x2, y2, r) { + // TODO Supportare r + 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((p2x - p1x) / Math.abs(p2y - p1y)), + b = Math.atan((p3x - p2x) / Math.abs(p2y - p3y)); + a = p1y < p2y ? Math.PI - a : a; + b = p3y < p2y ? 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 + }; + }, + + linepathRevert : function(path) { + var rev = [], anc = false; + for (var i = path.length - 1; i >= 0; i--) { + switch (path[i][0]) { + case "M" : case "L" : + if (!anc) + rev.push( [ rev.length ? "L" : "M", path[i][1], path[i][2] ] ); + else + rev.push( [ "C", anc[0], anc[1], anc[2], anc[3], path[i][1], path[i][2] ] ); + anc = false; + + break; + case "C" : + if (!anc) + rev.push( [ rev.length ? "L" : "M", path[i][5], path[i][6] ] ); + else + rev.push( [ "C", anc[0], anc[1], anc[2], anc[3], path[i][5], path[i][6] ] ); + anc = [ path[i][3], path[i][4], path[i][1], path[i][2] ]; + } + } + return rev; + }, + + linepath : function ( points, rounded ) { + var path = []; + if (rounded) { + var anc = false; + for (var j = 0, jj = points.length - 1; j < jj ; j++) { + if (j) { + 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] ]); + anc = [ a.x2, a.y2 ]; + } else { + path.push([ "M", points[j][0], points[j][1] ]); + anc = [ points[j][0], points[j][1] ]; + } + } + if (anc) + path.push([ "C", anc[0], anc[1], points[jj][0], points[jj][1], points[jj][0], points[jj][1] ]); + + } else + for (var i = 0; i < points.length; i++) { + var x = points[i][0], y = points[i][1]; + path.push([i == 0 ? "M" : "L", x, y]); + } + + return path; + }, + + lineareapath : function (points1, points2, rounded) { + var path = this.linepath(points1, rounded), path2 = this.linepathRevert(this.linepath(points2, rounded)); + + for (var i = 0; i < path2.length; i++) + path.push( !i ? [ "L", path2[0][1], path2[0][2] ] : path2[i] ); + + if (path.length) + path.push(['z']); + + return path; + }, + + /** + * 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)] ]; + 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]) { + 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': + // (x1 y1 x2 y2 x y)+ + newpath.push([o[0], o[1], o[2], o[3], o[4], 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': + // (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)]); + break; + case 'z': case 'Z': + newpath.push([o[0]]); + break; + } + } + + return newpath; + }, + + _movePathX : function(env, x, dx, marginlimit) { + if (!marginlimit) + return x + dx; + x = x + dx; + return dx > 0 && x > env.opt.width - env.opt.margins[1] ? env.opt.width - env.opt.margins[1] : (dx < 0 && x < env.opt.margins[3] ? env.opt.margins[3] : x); + }, + + _movePathY : function(env, y, dy, marginlimit) { + if (!marginlimit) + return y + dy; + y = y + dy; + return dy > 0 && y > env.opt.height - env.opt.margins[2] ? env.opt.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(path, prevprops) { + 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) { + path = this.preparePathShow(env, path); + + if (!paper) + paper = env.paper; + if (path.length == 1 && path[0][0] == 'CIRCLE') + return paper.circle(path[0][1], path[0][2], path[0][3]); + if (path.length == 1 && path[0][0] == 'TEXT') + return paper.text(path[0][2], path[0][3], path[0][1]); + var props = this.getSVGProps(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(this.preparePathShow(env, piece.path), piece.fullattr); + } + if (typeof piece.fullattr.opacity == 'undefined') + piece.fullattr.opacity = 1; + } + return piece.fullattr; + }, + + + show : function(env, pieces) { + pieces = this.getSortedPathData(pieces); + + common.animationStackStart(env); + + var previousElement = false; + for (var i = 0; i < pieces.length; i++) { + var piece = pieces[i]; + + if (typeof piece.show == 'undefined' || piece.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; + } + } + } + + common.animationStackEnd(env); + }, + + /** + * 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(); + caller._animationStackAnimateElement(a); + + while (stack.length > 0) { + var b = stack.pop(); + caller._animationStackAnimateElement(b, a); + } + } + if (delay > 0) + setTimeout(func, delay); + else + func(); + }, + + _animationStackAnimateElement : function (a, awith) { + //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 (awith) + a.object.animateWith(awith, a.props, a.speed, a.easing ? a.easing : 'linear', onEnd); + else + a.object.animate(a.props, a.speed, a.easing ? a.easing : 'linear', onEnd); + } + //else console.warn('SKIP', a.piece.animationInProgress, a.piece.path, a.piece); + } +} + +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; + }, + + 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 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; + +/*********************************************************************** + * 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 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; + +/*********************************************************************** + * ANIMATIONMANAGER + **********************************************************************/ + +$.elycharts.animationmanager = { + + beforeShow : function(env, pieces) { + if (!env.newopt) + this.startAnimation(env, pieces); + else + this.stepAnimation(env, pieces); + }, + + stepAnimation : function(env, pieces) { + // env.pieces sono i vecchi pieces, ed e' sempre un array completo di tutte le sezioni + // pieces sono i nuovi pezzi da mostrare, e potrebbe essere parziale + //console.warn('from1', common._clone(env.pieces)); + //console.warn('from2', common._clone(pieces)); + pieces = this._stepAnimationInt(env, env.pieces, pieces); + //console.warn('to', common._clone(pieces)); + }, + + _stepAnimationInt : function(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 ++) { + var animationProps = common.areaProps(env, section ? section : pieces1[i].section, serie ? serie : pieces1[i].serie); + if (animationProps && animationProps.stepAnimation) + animationProps = animationProps.stepAnimation; + else + animationProps = env.opt.features.animation.stepAnimation; + + // 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, show : true }; + newpiece.animation = { + element : pieces1[i].element ? pieces1[i].element : false, + speed : animationProps && animationProps.speed ? animationProps.speed : 300, + easing : animationProps && animationProps.easing ? animationProps.easing : '', + delay : animationProps && animationProps.delay ? animationProps.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') { + // Piece a singolo path + newpiece.animation = { + element : pieces1[i].element ? pieces1[i].element : false, + speed : animationProps && animationProps.speed ? animationProps.speed : 300, + easing : animationProps && animationProps.easing ? animationProps.easing : '', + delay : animationProps && animationProps.delay ? animationProps.delay : 0 + } + // Se non c'era elemento precedente deve gestire il fadeIn + if (!pieces1[i].element) + newpiece.animation.startAttr = {opacity : 0}; + + } else { + // Multiple path piece + newpiece.paths = this._stepAnimationInt(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; + }, + + 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.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 = common._mergeObjects(piece.attr, animationProps.propsTo); + if (animationProps.propsFrom) + piece.animation.startAttr = common._mergeObjects(piece.animation.startAttr, animationProps.propsFrom); + if (subSection && animationProps[subSection.toLowerCase() + 'PropsFrom']) + piece.animation.startAttr = common._mergeObjects(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.opt.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], 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], y ]); + npath[2].push([ piece.path[0][2][i][0], 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.opt.width - env.opt.margins[1]; + else if (props.subType == 1) + x = env.opt.direction != 'rtl' ? env.opt.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; + for (i = 0; i < arr.length; i++) + avg += arr[i][1]; + avg = avg / arr.length; + for (i = 0; i < arr.length; i++) + res.push([ arr[i][0], 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 y1 = arr[0][1]; + var y2 = arr[c - 1][1]; + + for (var i = 0; i < arr.length; i++) + res.push([arr[i][0], y1 + (y2 - y1) / (c - 1) * i]); + + 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 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; + +/*********************************************************************** + * 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(common.preparePathShow(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][1], path[0][3] * scale[0], path[0][4], path[0][5] - d, path[0][6] + d ] ]; + common.animationStackPush(env, piece, element, common.getSVGProps(common.preparePathShow(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 + 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(common.preparePathShow(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(common.preparePathShow(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(common.preparePathShow(env, ppath)), props.highlight.scaleSpeed, props.highlight.scaleEasing, 0, true); + env.highlighted.push({piece : ppiece, cfg : props.highlight}); + } + + 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(common.preparePathShow(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.opt.width - env.opt.margins[3] - env.opt.margins[1]) / (env.opt.labels.length > 0 ? env.opt.labels.length : 1); + var delta2 = (env.opt.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.opt.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.opt.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 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; + +/*********************************************************************** + * 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 = $('
' + label + '
').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 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; + +/*********************************************************************** + * FEATURE: LEGEND + **********************************************************************/ + +$.elycharts.legendmanager = { + + afterShow : function(env, pieces) { + if (!env.opt.legend || env.opt.legend.length == 0) + return; + + var props = env.opt.features.legend; + + if (props.x == 'auto') { + var autox = 1; + props.x = 0; + } + if (props.width == 'auto') { + var autowidth = 1; + props.width = env.opt.width; + } + + var borderPath = [ [ 'RECT', props.x, props.y, props.x + props.width, props.y + props.height, props.r ] ]; + var border = common.showPath(env, borderPath).attr(props.borderProps); + if (autox || autowidth) + border.hide(); + + 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 dprops = $.extend(true, {}, props.dotProps); + if (sprops.legend && sprops.legend.dotProps) + dprops = $.extend(true, dprops, sprops.legend.dotProps); + if (!dprops.fill && env.opt.type == 'pie') { + if (sprops.color) + dprops.fill = sprops.color; + if (sprops.plotProps && sprops.plotProps.fill) + dprops.fill = sprops.plotProps.fill; + } + var dtype = sprops.legend && sprops.legend.dotType ? sprops.legend.dotType : props.dotType; + var dwidth = sprops.legend && sprops.legend.dotWidth ? sprops.legend.dotWidth : props.dotWidth; + var dheight = sprops.legend && sprops.legend.dotHeight ? sprops.legend.dotHeight : props.dotHeight; + var dr = sprops.legend && sprops.legend.dotR ? sprops.legend.dotR : props.dotR; + var tprops = sprops.legend && sprops.legend.textProps ? sprops.legend.textProps : props.textProps; + + if (!props.horizontal) { + // Posizione dell'angolo in alto a sinistra + h = (props.height - props.margins[0] - props.margins[2]) / legendCount; + w = props.width - props.margins[1] - props.margins[3]; + x = Math.floor(props.x + props.margins[3]); + y = Math.floor(props.y + props.margins[0] + h * i); + } else { + h = props.height - props.margins[0] - props.margins[2]; + if (!props.itemWidth || props.itemWidth == 'fixed') { + w = (props.width - props.margins[1] - props.margins[3]) / legendCount; + x = Math.floor(props.x + props.margins[3] + w * i); + } else { + w = (props.width - props.margins[1] - props.margins[3]) - wauto; + x = props.x + props.margins[3] + wauto; + } + y = Math.floor(props.y + props.margins[0]); + } + + if (dtype == "rect") { + items.push(common.showPath(env, [ [ 'RECT', props.dotMargins[0] + x, y + Math.floor((h - dheight) / 2), props.dotMargins[0] + x + dwidth, y + Math.floor((h - dheight) / 2) + dheight, dr ] ]).attr(dprops)); + xd = props.dotMargins[0] + dwidth + props.dotMargins[1]; + } else if (dtype == "circle") { + items.push(common.showPath(env, [ [ 'CIRCLE', props.dotMargins[0] + x + dr, y + (h / 2), dr ] ]).attr(dprops)); + xd = props.dotMargins[0] + dr * 2 + props.dotMargins[1]; + } + + var text = data[j]; + var t = common.showPath(env, [ [ 'TEXT', text, x + xd, y + Math.ceil(h / 2) + ($.browser.msie ? 2 : 0) ] ]).attr({"text-anchor" : "start"}).attr(tprops); //.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) + props.width = wauto + props.margins[3] + props.margins[1] - 1; + if (autox) { + props.x = Math.floor((env.opt.width - props.width) / 2); + for (i in items) { + if (items[i].attrs.x) + items[i].attr('x', items[i].attrs.x + props.x); + else + items[i].attr('path', common.movePath(env, items[i].attrs.path, [props.x, 0])); + } + } + if (autowidth || autox) { + borderPath = [ [ 'RECT', props.x, props.y, props.x + props.width, props.y + props.height, props.r ] ]; + border.attr(common.getSVGProps(common.preparePathShow(env, borderPath))); + //border.attr({path : common.preparePathShow(env, common.getSVGPath(borderPath))}); + border.show(); + } + } +} + +$.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 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 = { + + afterShow : function(env, pieces) { + if (!env.opt.interactive) + return; + + if (env.mouseLayer) { + env.mouseLayer.remove(); + env.mouseLayer = null; + env.mousePaper.remove(); + env.mousePaper = null; + env.mouseTimer = null; + env.mouseAreas = null; + // Meglio fare anche l'unbind??? + } + + env.mouseLayer = $('
').css({position : 'absolute', 'z-index' : 20, opacity : 0}).prependTo(env.container); + env.mousePaper = common._RaphaelInstance(env.mouseLayer.get(0), env.opt.width, env.opt.height); + var paper = env.mousePaper; + + if (env.opt.features.debug.active && typeof DP_Debug != 'undefined') { + env.paper.text(env.opt.width, env.opt.height - 5, 'DEBUG').attr({ 'text-anchor' : 'end', stroke: 'red', opacity: .1 }); + paper.text(env.opt.width, env.opt.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.opt.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.opt.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 index in env.opt.labels) { + env.mouseAreas.push({ + path : [ [ 'RECT', start + index * delta, env.opt.margins[0], start + (index + 1) * delta, env.opt.height - env.opt.margins[2] ] ], + 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: "none", 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 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; + +/*********************************************************************** + * FEATURE: TOOLTIP + **********************************************************************/ + +$.elycharts.tooltipmanager = { + + afterShow : function(env, pieces) { + if (env.tooltipContainer) { + env.tooltipFrame.remove(); + env.tooltipFrame = null; + env.tooltipFrameElement = null; + env.tooltipContent.remove(); + env.tooltipContent = null; + env.tooltipContainer.remove(); + env.tooltipContainer = null; + } + + if (!$.elycharts.tooltipid) + $.elycharts.tooltipid = 0; + $.elycharts.tooltipid ++; + + // Preparo il tooltip + env.tooltipContainer = $('').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) { + 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; + }, + + onMouseEnter : 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; + + var tip = this.getTip(env, serie, index); + if (!tip) + return this.onMouseExit(env, serie, index, mouseAreaData); + + //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]; + + // 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) + env.tooltipFrameElement = env.tooltipFrame.rect(delta / 2, delta / 2, props.width, props.height, props.roundedCorners); + } + + 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 = 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; + + var tip = this.getTip(env, serie, index); + if (!tip) + return this.onMouseExit(env, serie, index, mouseAreaData); + + /*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(); + // 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 = 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; + + //env.tooltipContainer.unbind(); + env.tooltipContainer.fadeOut(env.opt.features.tooltip.fadeDelay); + + 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 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; + +/*********************************************************************** + * CHART: LINE/BAR + **********************************************************************/ + +$.elycharts.line = { + init : function($env) { + }, + + 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 (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]); + } + for (i = 0; i < showValues.length; i++) + plot.from.push(0); + + } 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]); + else + for (i = 0; i < showValues.length; i++) + plot.to.push(plot.from[i] + (cum += showValues[i])); + 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 = (opt.width - opt.margins[3] - opt.margins[1]) / (labels.length > 1 ? labels.length - 1 : 1); + var deltaBarX = (opt.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]; + + // 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 = (opt.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); + + var d = plot.to[i] > plot.max ? plot.max : (plot.to[i] < plot.min ? plot.min : plot.to[i]); + var x = Math.round((props.lineCenter ? deltaBarX / 2 : 0) + opt.margins[3] + i * (props.lineCenter ? deltaBarX : deltaX)); + var y = Math.round(opt.height - opt.margins[2] - deltaY * (d - plot.min)); + var dd = plot.from[i] > plot.max ? plot.max : (plot.from[i] < plot.min ? plot.min : plot.from[i]); + var yy = Math.round(opt.height - opt.margins[2] - deltaY * (dd - plot.min)) + ($.browser.msie ? 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 bwid = Math.floor((deltaBarX - opt.barMargins) / env.barno); + var bpad = bwid * (100 - props.barWidthPerc) / 200; + var boff = opt.barMargins / 2 + plot.barno * bwid; + + var x1 = Math.floor(opt.margins[3] + i * deltaBarX + boff + bpad); + var y1 = Math.round(opt.height - opt.margins[2] - deltaY * (plot.to[i] - plot.min)); + var y2 = Math.round(opt.height - opt.margins[2] - deltaY * (plot.from[i] - plot.min)); + + pieceBar.push({path : [ [ 'RECT', x1, y1, x1 + bwid - bpad * 2, y2 ] ], attr : props.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 }); + } + } + featuresmanager.beforeShow(env, pieces); + common.show(env, pieces); + featuresmanager.afterShow(env, pieces); + 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 = (opt.width - opt.margins[3] - opt.margins[1]) / (labels.length > 1 ? labels.length - 1 : 1); + var deltaBarX = (opt.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 = opt.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, opt.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 > opt.width)) { + labe.hide(); + labels[i] = false; + } else { + lastShownLabelRect = rect; + } + } + + // Apply rotation to the element. + if (axis.x.props.labelsRotate) { + 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((opt.width - opt.margins[1] - opt.margins[3]) / 2); + y = opt.height - opt.margins[2] + axis.x.props.titleDistance * ($.browser.msie ? 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 = (opt.height - opt.margins[2] - opt.margins[0]) / props.ny; + if (j == 'r') { + labx = opt.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 = opt.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 = opt.width - opt.margins[1] + axis[j].props.titleDistance * ($.browser.msie ? axis[j].props.titleDistanceIE : 1); + else + x = opt.margins[3] - axis[j].props.titleDistance * ($.browser.msie ? axis[j].props.titleDistanceIE : 1); + //paper.text(x, opt.margins[0] + Math.floor((opt.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); + attr.rotation = j == 'l' ? 270 : 90 + pieces.push({ section : 'Axis', serie : j, subSection : 'Title', path : [ [ 'TEXT', axis[j].props.title, x, opt.margins[0] + Math.floor((opt.height - opt.margins[0] - opt.margins[2]) / 2) ] ], 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 = (opt.height - opt.margins[2] - opt.margins[0]) / (ny ? ny : 1), + columnWidth = (opt.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", opt.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 + opt.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), opt.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), opt.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), opt.height - opt.margins[2] - props.ticks.size[1] ]); + tpath.push(["L", opt.margins[3] + Math.round(i * columnWidth), opt.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", opt.width - opt.margins[1] - props.ticks.size[1], opt.margins[0] + Math.round(i * rowHeight) ]); + tpath.push(["L", opt.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 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; + +/*********************************************************************** + * CHART: PIE + **********************************************************************/ + +$.elycharts.pie = { + init : function($env) { + }, + + draw : function(env) { + //var paper = env.paper; + var opt = env.opt; + + var w = env.opt.width; + var h = env.opt.height; + var r = env.opt.r ? env.opt.r : Math.floor((w < h ? w : h) / 2.5); + var cx = env.opt.cx ? env.opt.cx : Math.floor(w / 2); + var cy = env.opt.cy ? env.opt.cx : Math.floor(h / 2); + + 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); + 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); + 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'); + 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); + 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'}); + } + + featuresmanager.beforeShow(env, pieces); + common.show(env, pieces); + featuresmanager.afterShow(env, pieces); + return pieces; + } +} + +})(jQuery); diff --git a/SFDC-BrewApp/assets/chart/elycharts.min.js b/SFDC-BrewApp/assets/chart/elycharts.min.js new file mode 100755 index 0000000..e104ad9 --- /dev/null +++ b/SFDC-BrewApp/assets/chart/elycharts.min.js @@ -0,0 +1,8 @@ +/*!********************************************************************* + * ELYCHARTS v2.1.4-SNAPSHOT $Id: elycharts.min.js 52 2011-08-07 19:57:09Z stefano.bagnara@gmail.com $ + * A Javascript library to generate interactive charts with vectorial graphics. + * + * Copyright (c) 2010 Void Labs s.n.c. (http://void.it) + * Licensed under the MIT (http://creativecommons.org/licenses/MIT/) license. + **********************************************************************/ +(function(a){if(!a.elycharts){a.elycharts={}}a.elycharts.templates={common:{margins:[10,10,10,10],interactive:true,defaultSeries:{visible:true,tooltip:{active:true,width:100,height:50,roundedCorners:5,padding:[6,6],offset:[20,0],frameProps:{fill:"white","stroke-width":2},contentStyle:{"font-family":"Arial","font-size":"12px","line-height":"16px",color:"black"}},highlight:{scaleSpeed:100,scaleEasing:"",moveSpeed:100,moveEasing:"",restoreSpeed:0,restoreEasing:""},anchor:{},startAnimation:{type:"simple",speed:600,delay:0,propsFrom:{},propsTo:{},easing:""},label:{active:false,html:false,props:{fill:"black",stroke:"none","font-family":"Arial","font-size":"16px"},style:{cursor:"default"}}},series:{empty:{label:{active:false},tooltip:{active:false}}},features:{tooltip:{fadeDelay:100,moveDelay:300},mousearea:{type:"single",indexCenter:"auto",areaMoveDelay:500,syncTag:false,onMouseEnter:false,onMouseExit:false,onMouseChanged:false,onMouseOver:false,onMouseOut:false},highlight:{indexHighlightProps:{opacity:1}},animation:{startAnimation:{speed:600,delay:0,easing:""},stepAnimation:{speed:600,delay:0,easing:""}},frameAnimation:{active:false,cssFrom:{opacity:0},cssTo:{opacity:1},speed:"slow",easing:"linear"},pixelWorkAround:{active:true},label:{},shadows:{active:false,offset:[2,2],props:{"stroke-width":0,"stroke-opacity":0,fill:"black","fill-opacity":0.3}},balloons:{active:false,style:{},padding:[5,5],left:10,line:[[0,0],[0,0]],lineProps:{}},legend:{horizontal:false,x:"auto",y:10,width:"auto",height:20,itemWidth:"fixed",margins:[0,0,0,0],dotMargins:[10,5],borderProps:{fill:"white",stroke:"black","stroke-width":1},dotType:"rect",dotWidth:10,dotHeight:10,dotR:4,dotProps:{type:"rect",width:10,height:10},textProps:{font:"12px Arial",fill:"#000"}},debug:{active:false}},nop:0},line:{template:"common",barMargins:0,defaultAxis:{normalize:2,min:0,labels:false,labelsDistance:8,labelsRotate:0,labelsProps:{font:"10px Arial",fill:"#000"},titleDistance:25,titleDistanceIE:0.75,titleProps:{font:"12px Arial",fill:"#000","font-weight":"bold"}},axis:{x:{titleDistanceIE:1.2}},defaultSeries:{type:"line",axis:"l",cumulative:false,rounded:1,lineCenter:"auto",plotProps:{"stroke-width":1,"stroke-linejoin":"round"},barWidthPerc:100,fill:false,fillProps:{stroke:"none","stroke-width":0,"stroke-opacity":0,opacity:0.3},dot:false,dotProps:{size:4,stroke:"#000",zindex:5},dotShowOnNull:false,mouseareaShowOnNull:false,startAnimation:{plotPropsFrom:false,fillPropsFrom:false,dotPropsFrom:false,shadowPropsFrom:false}},features:{grid:{nx:"auto",ny:4,draw:false,forceBorder:false,props:{stroke:"#e0e0e0","stroke-width":1},extra:[0,0,0,0],labelsCenter:"auto",evenVProps:false,oddVProps:false,evenHProps:false,oddHProps:false,ticks:{active:[false,false,false],size:[10,10],props:{stroke:"#e0e0e0","stroke-width":1}}}},nop:0},pie:{template:"common",startAngle:0,clockwise:false,valueThresold:0.006,defaultSeries:{}},funnel:{template:"common",rh:0,method:"width",topSector:0,topSectorProps:{fill:"#d0d0d0"},bottomSector:0.1,bottomSectorProps:{fill:"#d0d0d0"},edgeProps:{fill:"#c0c0c0","stroke-width":1,opacity:1},nop:0},barline:{template:"common",direction:"ltr"}}})(jQuery);(function(f){if(!f.elycharts){f.elycharts={}}f.elycharts.lastId=0;f.fn.chart=function(h){if(!this.length){return this}var i=this.data("elycharts_env");if(typeof h=="string"){if(h.toLowerCase()=="config"){return i?i.opt:false}if(h.toLowerCase()=="clear"){if(i){i.paper.clear();this.html("");this.data("elycharts_env",false)}}}else{if(!i){if(h){h=b(h)}if(!h||!h.type||!f.elycharts.templates[h.type]){alert("ElyCharts ERROR: chart type is not specified");return false}i=g(this,h);e(i,h);i.pieces=f.elycharts[i.opt.type].draw(i);this.data("elycharts_env",i)}else{h=d(h,i.opt);i.oldopt=c._clone(i.opt);i.opt=f.extend(true,i.opt,h);i.newopt=h;e(i,h);i.pieces=f.elycharts[i.opt.type].draw(i)}}return this};function g(j,h){if(!h.width){h.width=j.width()}if(!h.height){h.height=j.height()}var i={id:f.elycharts.lastId++,paper:c._RaphaelInstance(j.get()[0],h.width,h.height),container:j,plots:[],opt:h};i.paper.rect(0,0,1,1).attr({opacity:0});f.elycharts[h.type].init(i);return i}function e(i,h){if(h.style){i.container.css(h.style)}}function b(h){var i;if(f.elysia_charts){if(f.elysia_charts.default_options){for(i in f.elysia_charts.default_options){f.elycharts.templates[i]=f.elysia_charts.default_options[i]}}if(f.elysia_charts.templates){for(i in f.elysia_charts.templates){f.elycharts.templates[i]=f.elysia_charts.templates[i]}}}while(h.template){var j=h.template;delete h.template;h=f.extend(true,{},f.elycharts.templates[j],h)}if(!h.template&&h.type){h.template=h.type;while(h.template){j=h.template;delete h.template;h=f.extend(true,{},f.elycharts.templates[j],h)}}return d(h,h)}function d(h,i){if(h.type=="pie"||h.type=="funnel"){if(h.values&&f.isArray(h.values)&&!f.isArray(h.values[0])){h.values={root:h.values}}if(h.tooltips&&f.isArray(h.tooltips)&&!f.isArray(h.tooltips[0])){h.tooltips={root:h.tooltips}}if(h.anchors&&f.isArray(h.anchors)&&!f.isArray(h.anchors[0])){h.anchors={root:h.anchors}}if(h.balloons&&f.isArray(h.balloons)&&!f.isArray(h.balloons[0])){h.balloons={root:h.balloons}}if(h.legend&&f.isArray(h.legend)&&!f.isArray(h.legend[0])){h.legend={root:h.legend}}}if(h.defaultSeries){var n=i.type!="line"?i.type:(h.defaultSeries.type?h.defaultSeries.type:(i.defaultSeries.type?i.defaultSeries.type:"line"));a(h.defaultSeries,n,i);if(h.defaultSeries.stackedWith){h.defaultSeries.stacked=h.defaultSeries.stackedWith;delete h.defaultSeries.stackedWith}}if(h.series){for(var l in h.series){var k=i.type!="line"?i.type:(h.series[l].type?h.series[l].type:(i.series[l].type?i.series[l].type:(n?n:"line")));a(h.series[l],k,i);if(h.series[l].values){for(var m in h.series[l].values){a(h.series[l].values[m],k,i)}}if(h.series[l].stackedWith){h.series[l].stacked=h.series[l].stackedWith;delete h.series[l].stackedWith}}}if(h.type=="line"){if(!h.features){h.features={}}if(!h.features.grid){h.features.grid={}}if(typeof h.gridNX!="undefined"){h.features.grid.nx=h.gridNX;delete h.gridNX}if(typeof h.gridNY!="undefined"){h.features.grid.ny=h.gridNY;delete h.gridNY}if(typeof h.gridProps!="undefined"){h.features.grid.props=h.gridProps;delete h.gridProps}if(typeof h.gridExtra!="undefined"){h.features.grid.extra=h.gridExtra;delete h.gridExtra}if(typeof h.gridForceBorder!="undefined"){h.features.grid.forceBorder=h.gridForceBorder;delete h.gridForceBorder}if(h.defaultAxis&&h.defaultAxis.normalize&&(h.defaultAxis.normalize=="auto"||h.defaultAxis.normalize=="autony")){h.defaultAxis.normalize=2}if(h.axis){for(var j in h.axis){if(h.axis[j]&&h.axis[j].normalize&&(h.axis[j].normalize=="auto"||h.axis[j].normalize=="autony")){h.axis[j].normalize=2}}}}return h}function a(k,h,j){if(k.color){var i=k.color;if(!k.plotProps){k.plotProps={}}if(h=="line"){if(k.plotProps&&!k.plotProps.stroke&&!j.defaultSeries.plotProps.stroke){k.plotProps.stroke=i}}else{if(k.plotProps&&!k.plotProps.fill&&!j.defaultSeries.plotProps.fill){k.plotProps.fill=i}}if(!k.tooltip){k.tooltip={}}if(!k.tooltip.frameProps&&j.defaultSeries.tooltip.frameProps){k.tooltip.frameProps={}}if(k.tooltip&&k.tooltip.frameProps&&!k.tooltip.frameProps.stroke&&!j.defaultSeries.tooltip.frameProps.stroke){k.tooltip.frameProps.stroke=i}if(!k.legend){k.legend={}}if(!k.legend.dotProps){k.legend.dotProps={}}if(k.legend.dotProps&&!k.legend.dotProps.fill){k.legend.dotProps.fill=i}if(h=="line"){if(!k.dotProps){k.dotProps={}}if(k.dotProps&&!k.dotProps.fill&&!j.defaultSeries.dotProps.fill){k.dotProps.fill=i}if(!k.fillProps){k.fillProps={}}if(k.fillProps&&!k.fillProps.fill&&!j.defaultSeries.fillProps.fill){k.fillProps.fill=i}}}}f.elycharts.common={_RaphaelInstance:function(l,i,j){var k=Raphael(l,i,j);k.customAttributes.slice=function(h,s,o,n,q,p){a1=360-p;a2=360-q;var m=(a2-a1)>180;a1=(a1%360)*Math.PI/180;a2=(a2%360)*Math.PI/180;if(a1==a2&&q!=p){a2+=359.99*Math.PI/180}return{path:n?[["M",h+o*Math.cos(a1),s+o*Math.sin(a1)],["A",o,o,0,+m,1,h+o*Math.cos(a2),s+o*Math.sin(a2)],["L",h+n*Math.cos(a2),s+n*Math.sin(a2)],["A",n,n,0,+m,0,h+n*Math.cos(a1),s+n*Math.sin(a1)],["z"]]:[["M",h,s],["l",o*Math.cos(a1),o*Math.sin(a1)],["A",o,o,0,+m,1,h+o*Math.cos(a2),s+o*Math.sin(a2)],["z"]]}};return k},_clone:function(j){if(j==null||typeof(j)!="object"){return j}if(j.constructor==Array){return[].concat(j)}var h=new j.constructor();for(var i in j){h[i]=this._clone(j[i])}return h},_mergeObjects:function(i,h){return f.extend(true,i,h)},compactUnits:function(l,j){for(var k=j.length-1;k>=0;k--){var h=l/Math.pow(1000,k+1);if(h>=1&&h*10%10==0){return h+j[k]}}return l},getElementOriginalAttrs:function(i){var h=f(i.node).data("original-attr");if(!h){h=i.attr();f(i.node).data("original-attr",h)}return h},findInPieces:function(l,n,k,h,m){for(var j=0;j=0&&i.values&&i.values[h]){i=this._mergeObjects(i,i.values[h])}}}else{i=this._clone(j.opt[l.toLowerCase()]);if(typeof k=="undefined"||!k){if(j.opt[m.toLowerCase()]&&j.opt[m.toLowerCase()][l.toLowerCase()]){i=this._mergeObjects(i,j.opt[m.toLowerCase()][l.toLowerCase()])}}else{if(j.opt["default"+m]&&j.opt["default"+m][l.toLowerCase()]){i=this._mergeObjects(i,j.opt["default"+m][l.toLowerCase()])}if(j.opt[m.toLowerCase()]&&j.opt[m.toLowerCase()][k]&&j.opt[m.toLowerCase()][k][l.toLowerCase()]){i=this._mergeObjects(i,j.opt[m.toLowerCase()][k][l.toLowerCase()])}if(i&&(typeof h!="undefined")&&h>0&&i.values&&i.values[h]){i=this._mergeObjects(i,i.values[h])}}}return i},absrectpath:function(i,k,h,j,l){return[["M",i,k],["L",i,j],["L",h,j],["L",h,k],["z"]]},linepathAnchors:function(j,i,u,s,p,o,l){var h=1;if(l&&l.length){h=l[1];l=l[0]}if(!l){l=1}var m=(u-j)/2,k=(p-u)/2,v=Math.atan((u-j)/Math.abs(s-i)),t=Math.atan((p-u)/Math.abs(s-o));v=i0){v=0;t=0}else{if(Math.abs(v-Math.PI/2)=0;j--){switch(l[j][0]){case"M":case"L":if(!k){h.push([h.length?"L":"M",l[j][1],l[j][2]])}else{h.push(["C",k[0],k[1],k[2],k[3],l[j][1],l[j][2]])}k=false;break;case"C":if(!k){h.push([h.length?"L":"M",l[j][5],l[j][6]])}else{h.push(["C",k[0],k[1],k[2],k[3],l[j][5],l[j][6]])}k=[l[j][3],l[j][4],l[j][1],l[j][2]]}}return h},linepath:function(r,h){var s=[];if(h){var m=false;for(var k=0,n=r.length-1;k0&&h>j.opt.width-j.opt.margins[1]?j.opt.width-j.opt.margins[1]:(i<0&&h0&&k>i.opt.height-i.opt.margins[2]?i.opt.height-i.opt.margins[2]:(h<0&&k0)){j[i]=k}else{if(!h){return false}}return j},showPath:function(l,m,n){m=this.preparePathShow(l,m);if(!n){n=l.paper}if(m.length==1&&m[0][0]=="CIRCLE"){return n.circle(m[0][1],m[0][2],m[0][3])}if(m.length==1&&m[0][0]=="TEXT"){return n.text(m[0][2],m[0][3],m[0][1])}var j=this.getSVGProps(m);var i=false;for(var h in j){i=true;break}return j&&i?n.path().attr(j):false},preparePathShow:function(h,i){return h.opt.features.pixelWorkAround.active?this.movePath(h,this._clone(i),[0.5,0.5],false,true):i},getPieceFullAttr:function(i,h){if(!h.fullattr){h.fullattr=this._clone(h.attr);if(h.path){switch(h.path[0][0]){case"CIRCLE":var j=this.preparePathShow(i,h.path);h.fullattr.cx=j[0][1];h.fullattr.cy=j[0][2];h.fullattr.r=j[0][3];break;case"TEXT":case"DOMELEMENT":case"RELEMENT":break;default:h.fullattr=this.getSVGProps(this.preparePathShow(i,h.path),h.fullattr)}}if(typeof h.fullattr.opacity=="undefined"){h.fullattr.opacity=1}}return h.fullattr},show:function(l,m){m=this.getSortedPathData(m);c.animationStackStart(l);var h=false;for(var j=0;jn?1:(j.posi.pos?1:0)))})},animationStackStart:function(h){if(!h.animationStackDepth||h.animationStackDepth==0){h.animationStackDepth=0;h.animationStack={}}h.animationStackDepth++},animationStackEnd:function(i){i.animationStackDepth--;if(i.animationStackDepth==0){for(var h in i.animationStack){this._animationStackAnimate(i.animationStack[h],h);delete i.animationStack[h]}i.animationStack={}}},animationStackPush:function(l,k,j,h,n,o,i,m){if(typeof i=="undefined"){i=0}if(!l.animationStackDepth||l.animationStackDepth==0){this._animationStackAnimate([{piece:k,object:j,props:h,speed:n,easing:o,force:m}],i)}else{if(!l.animationStack[i]){l.animationStack[i]=[]}l.animationStack[i].push({piece:k,object:j,props:h,speed:n,easing:o,force:m})}},_animationStackAnimate:function(h,j){var i=this;var k=function(){var m=h.pop();i._animationStackAnimateElement(m);while(h.length>0){var l=h.pop();i._animationStackAnimateElement(l,m)}};if(j>0){setTimeout(k,j)}else{k()}},_animationStackAnimateElement:function(h,j){if(h.force||!h.piece.animationInProgress){h.object.stop();if(!h.props){h.props={opacity:0}}if(!h.speed||h.speed<=0){h.object.attr(h.props);h.piece.animationInProgress=false;return}h.piece.animationInProgress=true;var i=function(){h.piece.animationInProgress=false};if(j){h.object.animateWith(j,h.props,h.speed,h.easing?h.easing:"linear",i)}else{h.object.animate(h.props,h.speed,h.easing?h.easing:"linear",i)}}}};var c=f.elycharts.common;f.elycharts.featuresmanager={managers:[],initialized:false,register:function(i,h){f.elycharts.featuresmanager.managers.push([h,i]);f.elycharts.featuresmanager.initialized=false},init:function(){f.elycharts.featuresmanager.managers.sort(function(i,h){return i[0]0){var h=f.onAnchors.pop();c(h[0]).removeClass(h[1])}}};c.elycharts.featuresmanager.register(c.elycharts.anchormanager,30)})(jQuery);(function(b){var a=b.elycharts.common;b.elycharts.animationmanager={beforeShow:function(c,d){if(!c.newopt){this.startAnimation(c,d)}else{this.stepAnimation(c,d)}},stepAnimation:function(c,d){d=this._stepAnimationInt(c,c.pieces,d)},_stepAnimationInt:function(m,d,c,o,k,h){var f=[],l;var e=0;for(var g=0;g=c.length||!a.samePiecePath(d[g],c[e]))){if(!h){d[g].show=false;f.push(d[g])}else{l={path:false,attr:false,show:true};l.animation={element:d[g].element?d[g].element:false,speed:n&&n.speed?n.speed:300,easing:n&&n.easing?n.easing:"",delay:n&&n.delay?n.delay:0};f.push(l)}}else{l=c?c[e]:{path:false,attr:false};l.show=true;if(typeof d[g].paths=="undefined"){l.animation={element:d[g].element?d[g].element:false,speed:n&&n.speed?n.speed:300,easing:n&&n.easing?n.easing:"",delay:n&&n.delay?n.delay:0};if(!d[g].element){l.animation.startAttr={opacity:0}}}else{l.paths=this._stepAnimationInt(m,d[g].paths,c[e].paths,d[g].section,d[g].serie,true)}f.push(l);e++}}if(c){for(;e1){for(e=0;!g.paths[e].path&&e=0;e--){}d=g.paths[e].path?a.getY(g.paths[e].path[0]):0;for(e=0;e1){for(e=0;!g.paths[e].path&&e=0;e--){}d=g.paths[e].path?a.getY(g.paths[e].path[0]):0;for(e=0;e0){var e=d.highlighted.pop();if(e.piece){if(c){a.animationStackPush(d,e.piece,e.piece.element,a.getPieceFullAttr(d,e.piece),e.cfg.restoreSpeed,e.cfg.restoreEasing,0,true)}}else{e.element.remove()}}}},afterShow:function(c,d){if(c.highlighted&&c.highlighted.length>0){this.removeHighlighted(c,false)}c.highlighted=[]},onMouseOver:function(C,s,j,G){var r,c;for(var A=0;A90){D=90}r=[["SLICE",r[0][1],r[0][1],r[0][3]*I[0],r[0][4],r[0][5]-D,r[0][6]+D]];a.animationStackPush(C,e,c,a.getSVGProps(a.preparePathShow(C,r)),f.highlight.scaleSpeed,f.highlight.scaleEasing)}else{if(C.opt.type=="funnel"){var o=(e.rect[2]-e.rect[0])*(I[0]-1)/2;var m=(e.rect[3]-e.rect[1])*(I[1]-1)/2;a.animationStackStart(C);r=[a.movePath(C,[r[0]],[-o,-m])[0],a.movePath(C,[r[1]],[+o,-m])[0],a.movePath(C,[r[2]],[+o,+m])[0],a.movePath(C,[r[3]],[-o,+m])[0],r[4]];a.animationStackPush(C,e,c,a.getSVGProps(a.preparePathShow(C,r)),f.highlight.scaleSpeed,f.highlight.scaleEasing,0,true);q=false;if(j>0){F=G.pieces[A].paths[j-1];q=F.element;v=F.path}else{F=a.findInPieces(G.pieces,"Sector","top");if(F){q=F.element;v=F.path}}if(q){v=[v[0],v[1],a.movePath(C,[v[2]],[+o,-m])[0],a.movePath(C,[v[3]],[-o,-m])[0],v[4]];a.animationStackPush(C,F,q,a.getSVGProps(a.preparePathShow(C,v)),f.highlight.scaleSpeed,f.highlight.scaleEasing,0,true);C.highlighted.push({piece:F,cfg:f.highlight})}q=false;if(j0?C.opt.labels.length:1);var y=(C.opt.width-C.opt.margins[3]-C.opt.margins[1])/(C.opt.labels.length>1?C.opt.labels.length-1:1);var l=true;switch(p){case"bar":r=[["RECT",C.opt.margins[3]+j*z,C.opt.margins[0],C.opt.margins[3]+(j+1)*z,C.opt.height-C.opt.margins[2]]];break;case"line":l=false;case"barline":var k=Math.round((l?z/2:0)+C.opt.margins[3]+j*(l?z:y));r=[["M",k,C.opt.margins[0]],["L",k,C.opt.height-C.opt.margins[2]]]}if(r){c=a.showPath(C,r).attr(C.opt.features.highlight.indexHighlightProps);C.highlighted.push({element:c,attr:false,cfg:C.opt.features.highlight})}}},onMouseOut:function(e,f,d,c){this.removeHighlighted(e,true)}};b.elycharts.featuresmanager.register(b.elycharts.highlightmanager,21)})(jQuery);(function(b){var a=b.elycharts.common;b.elycharts.labelmanager={beforeShow:function(f,g){if(!a.executeIfChanged(f,["labels","values","series"])){return}if(f.opt.labels&&(f.opt.type=="pie"||f.opt.type=="funnel")){var j=false;var h;for(var d=0;d"+r+"").css(g).prependTo(n.container)}else{i=b(r).css(g).prependTo(n.container)}if(n.opt.features.debug.active&&i.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 e=d[0];var c=d[1];if(!j.label.frameAnchor||j.label.frameAnchor[0]=="middle"){e-=i.width()/2}else{if(j.label.frameAnchor&&j.label.frameAnchor[0]=="end"){e-=i.width()}}if(!j.label.frameAnchor||j.label.frameAnchor[1]=="middle"){c-=i.height()/2}else{if(j.label.frameAnchor&&j.label.frameAnchor[1]=="top"){c-=i.height()}}if(f){i.css({margin:c+"px 0 0 "+e+"px",opacity:m})}else{i.css({margin:c+"px 0 0 "+e+"px"})}return{path:[["DOMELEMENT",i]],attr:false}}}return false}};b.elycharts.featuresmanager.register(b.elycharts.labelmanager,5)})(jQuery);(function(b){var a=b.elycharts.common;b.elycharts.legendmanager={afterShow:function(H,e){if(!H.opt.legend||H.opt.legend.length==0){return}var c=H.opt.features.legend;if(c.x=="auto"){var A=1;c.x=0}if(c.width=="auto"){var k=1;c.width=H.opt.width}var p=[["RECT",c.x,c.y,c.x+c.width,c.y+c.height,c.r]];var B=a.showPath(H,p).attr(c.borderProps);if(A||k){B.hide()}var D=0;var s=[];var d=0;var z,K,G,o,n,m,C;for(z in H.opt.legend){if(H.opt.type!="pie"){d++}else{d+=H.opt.legend[z].length}}var F=0;for(z in H.opt.legend){if(H.opt.type!="pie"){K=[H.opt.legend[z]]}else{K=H.opt.legend[z]}for(var E=0;E(o-C)&&q.getBBox().width>10){r=r.substring(0,r.length-1);q.attr({text:r})}q.show();if(c.horizontal&&c.itemWidth=="auto"){D+=C+q.getBBox().width+4}else{if(!c.horizontal&&k){D=q.getBBox().width+C>D?q.getBBox().width+C:D}else{D+=o}}F++}}if(k){c.width=D+c.margins[3]+c.margins[1]-1}if(A){c.x=Math.floor((H.opt.width-c.width)/2);for(F in s){if(s[F].attrs.x){s[F].attr("x",s[F].attrs.x+c.x)}else{s[F].attr("path",a.movePath(H,s[F].attrs.path,[c.x,0]))}}}if(k||A){p=[["RECT",c.x,c.y,c.x+c.width,c.y+c.height,c.r]];B.attr(a.getSVGProps(a.preparePathShow(H,p)));B.show()}}};b.elycharts.featuresmanager.register(b.elycharts.legendmanager,90)})(jQuery);(function(c){var a=c.elycharts.featuresmanager;var b=c.elycharts.common;c.elycharts.mousemanager={afterShow:function(m,g){if(!m.opt.interactive){return}if(m.mouseLayer){m.mouseLayer.remove();m.mouseLayer=null;m.mousePaper.remove();m.mousePaper=null;m.mouseTimer=null;m.mouseAreas=null}m.mouseLayer=c("
").css({position:"absolute","z-index":20,opacity:0}).prependTo(m.container);m.mousePaper=b._RaphaelInstance(m.mouseLayer.get(0),m.opt.width,m.opt.height);var f=m.mousePaper;if(m.opt.features.debug.active&&typeof DP_Debug!="undefined"){m.paper.text(m.opt.width,m.opt.height-5,"DEBUG").attr({"text-anchor":"end",stroke:"red",opacity:0.1});f.text(m.opt.width,m.opt.height-5,"DEBUG").attr({"text-anchor":"end",stroke:"red",opacity:0.1}).click(function(){DP_Debug.dump(m.opt,"",false,4)})}var k,h;m.mouseAreas=[];if(m.opt.features.mousearea.type=="single"){for(k=0;k=1&&(g[k].path[0][0]=="LINE"||g[k].path[0][0]=="LINEAREA")){for(h=0;h0?m.opt.labels.length:1);e=m.opt.margins[3]}else{p=(m.opt.width-m.opt.margins[3]-m.opt.margins[1])/(m.opt.labels.length>1?m.opt.labels.length-1:1);e=m.opt.margins[3]-p/2}for(var l in m.opt.labels){m.mouseAreas.push({path:[["RECT",e+l*p,m.opt.margins[0],e+(l+1)*p,m.opt.height-m.opt.margins[2]]],piece:false,pieces:g,index:parseInt(l),props:m.opt.defaultSeries})}}var o=false;if(!m.opt.features.mousearea.syncTag){m.mouseareaenv={chartEnv:false,mouseObj:false,caller:false,inArea:-1,timer:false};o=m.mouseareaenv}else{if(!c.elycharts.mouseareaenv){c.elycharts.mouseareaenv={}}if(!c.elycharts.mouseareaenv[m.opt.features.mousearea.syncTag]){c.elycharts.mouseareaenv[m.opt.features.mousearea.syncTag]={chartEnv:false,mouseObj:false,caller:false,inArea:-1,timer:false}}o=c.elycharts.mouseareaenv[m.opt.features.mousearea.syncTag]}for(k=0;k