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 = $('