package gui // statisticsGui.go implements pages to view statistics about the Seekia network import "fyne.io/fyne/v2" import "fyne.io/fyne/v2/canvas" import "fyne.io/fyne/v2/container" import "fyne.io/fyne/v2/data/binding" import "fyne.io/fyne/v2/layout" import "fyne.io/fyne/v2/theme" import "fyne.io/fyne/v2/widget" import "seekia/internal/appMemory" import "seekia/internal/badgerDatabase" import "seekia/internal/createCharts" import "seekia/internal/globalSettings" import "seekia/internal/helpers" import "seekia/internal/network/appNetworkType/getAppNetworkType" import "seekia/internal/profiles/attributeDisplay" import "seekia/internal/profiles/userStatistics" import "seekia/internal/statisticsDatum" import "errors" import "image" import "strings" import "time" import "sync" func setNetworkStatisticsPage(window fyne.Window, previousPage func()){ currentPage := func(){setNetworkStatisticsPage(window, previousPage)} title := getPageTitleCentered("Network Statistics") backButton := getBackButtonCentered(previousPage) description := getLabelCentered(translate("View statistics about the Seekia network.")) mateStatisticsButton := widget.NewButton("Mate Statistics", func(){ setNetworkUserStatisticsPage(window, "Mate", currentPage) }) hostStatisticsButton := widget.NewButton("Host Statistics", func(){ setNetworkUserStatisticsPage(window, "Host", currentPage) }) moderatorStatisticsButton := widget.NewButton("Moderator Statistics", func(){ setNetworkUserStatisticsPage(window, "Moderator", currentPage) }) messageStatisticsButton := widget.NewButton("Message Statistics", func(){ //TODO showUnderConstructionDialog(window) }) buttonsGrid := getContainerCentered(container.NewGridWithColumns(1, mateStatisticsButton, hostStatisticsButton, moderatorStatisticsButton, messageStatisticsButton)) page := container.NewVBox(title, backButton, widget.NewSeparator(), description, widget.NewSeparator(), buttonsGrid) setPageContent(page, window) } func setNetworkUserStatisticsPage(window fyne.Window, identityType string, previousPage func()){ if (identityType != "Mate" && identityType != "Host" && identityType != "Moderator"){ setErrorEncounteredPage(window, errors.New("setUserStatisticsPage called with invalid identityType: " + identityType), previousPage) return } setLoadingScreen(window, identityType + " Statistics", "Loading " + identityType + " statistics page...") currentPage := func(){setNetworkUserStatisticsPage(window, identityType, previousPage)} title := getPageTitleCentered(identityType + " Statistics") backButton := getBackButtonCentered(previousPage) description1 := getLabelCentered("Below are statistics about " + identityType + " profiles and users.") description2 := widget.NewLabel("Be aware that these statistics may not represent the entire Seekia network.") description2HelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){ setNetworkStatisticsInaccuracyWarningPage(window, currentPage) }) description2Row := container.NewHBox(layout.NewSpacer(), description2, description2HelpButton, layout.NewSpacer()) profileHashesList, err := badgerDatabase.GetAllProfileHashes(identityType) if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } numberOfUserProfiles := len(profileHashesList) profileIdentityHashesList, err := badgerDatabase.GetAllProfileIdentityHashes(identityType) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } numberOfUserIdentities := len(profileIdentityHashesList) numberOfUserProfilesString := helpers.ConvertIntToString(numberOfUserProfiles) numberOfUserProfilesLabel := widget.NewLabel("Number of " + identityType + " Profiles:") numberOfUserProfilesText := getBoldLabel(numberOfUserProfilesString) numberOfUserProfilesRow := container.NewHBox(layout.NewSpacer(), numberOfUserProfilesLabel, numberOfUserProfilesText, layout.NewSpacer()) numberOfUserIdentitiesString := helpers.ConvertIntToString(numberOfUserIdentities) numberOfUserIdentitiesLabel := widget.NewLabel("Number of " + identityType + " Identities:") numberOfUserIdentitiesText := getBoldLabel(numberOfUserIdentitiesString) numberOfUserIdentitiesRow := container.NewHBox(layout.NewSpacer(), numberOfUserIdentitiesLabel, numberOfUserIdentitiesText, layout.NewSpacer()) //TODO: add % of users who are disabled. //TODO: Add more statistics getIdentityTypeDefaultAttribute := func()string{ if (identityType == "Mate"){ return "Age" } if (identityType == "Host"){ return "SeekiaVersion" } // identityType == "Moderator" return "IdentityScore" } identityTypeDefaultAttribute := getIdentityTypeDefaultAttribute() attributeStatisticsButton := getWidgetCentered(widget.NewButtonWithIcon("View Attribute Statistics", theme.VisibilityIcon(), func(){ setViewUserAttributeStatisticsPage_BarChart(window, identityType, identityTypeDefaultAttribute, "Number Of Users", " users", true, false, false, nil, false, nil, nil, currentPage) })) page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2Row, widget.NewSeparator(), numberOfUserProfilesRow, numberOfUserIdentitiesRow, attributeStatisticsButton) setPageContent(page, window) } func setViewUserAttributeStatisticsPage_BarChart( window fyne.Window, identityType string, xAxisAttributeName string, yAxisAttribute string, yAxisUnits string, showYAxisPercentage bool, statisticsReady bool, anyUsersExist bool, statisticsDatumsList []statisticsDatum.StatisticsDatum, groupingPerformed bool, groupedStatisticsDatumsList []statisticsDatum.StatisticsDatum, chartImage image.Image, previousPage func()){ currentPage := func(){setViewUserAttributeStatisticsPage_BarChart(window, identityType, xAxisAttributeName, yAxisAttribute, yAxisUnits, showYAxisPercentage, statisticsReady, anyUsersExist, statisticsDatumsList, groupingPerformed, groupedStatisticsDatumsList, chartImage, previousPage)} pageIdentifier, err := helpers.GetNewRandomHexString(16) if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } appMemory.SetMemoryEntry("CurrentViewedPage", pageIdentifier) checkIfPageHasChangedFunction := func()bool{ exists, currentViewedPage := appMemory.GetMemoryEntry("CurrentViewedPage") if (exists == true && currentViewedPage == pageIdentifier){ return false } return true } title := getPageTitleCentered("User Attribute Statistics - Bar Chart") backButton := getBackButtonCentered(previousPage) identityTypeLabel := getBoldLabelCentered("Identity Type") handleIdentityTypeSelectFunction := func(newIdentityType string){ if (newIdentityType == identityType){ return } if (newIdentityType == "Mate"){ setViewUserAttributeStatisticsPage_BarChart(window, "Mate", "Age", "Number Of Users", " Users", true, false, false, nil, false, nil, nil, previousPage) return } if (newIdentityType == "Host"){ setViewUserAttributeStatisticsPage_BarChart(window, "Host", "SeekiaVersion", "Number Of Users", " Users", true, false, false, nil, false, nil, nil, previousPage) return } // newIdentityType == "Moderator" setViewUserAttributeStatisticsPage_BarChart(window, "Moderator", "IdentityScore", "Number Of Users", " Users", true, false, false, nil, false, nil, nil, previousPage) } identityTypesList := []string{"Mate", "Host", "Moderator"} identityTypeSelector := widget.NewSelect(identityTypesList, handleIdentityTypeSelectFunction) identityTypeSelector.Selected = identityType identityTypeColumn := container.NewVBox(identityTypeLabel, identityTypeSelector) chartTypeLabel := getBoldLabelCentered("Chart Type") chartTypesList := []string{"Bar Chart", "Donut Chart"} handleChartTypeSelectFunction := func(newChartType string){ if (newChartType == "Bar Chart"){ return } // newChartType == "Donut Chart" setViewUserAttributeStatisticsPage_DonutChart(window, identityType, xAxisAttributeName, false, false, nil, false, nil, nil, previousPage) } chartTypeSelector := widget.NewSelect(chartTypesList, handleChartTypeSelectFunction) chartTypeSelector.Selected = "Bar Chart" chartTypeColumn := container.NewVBox(chartTypeLabel, chartTypeSelector) xAxisAttributeTitle, xAxisIsNumerical, formatXAxisValuesFunction, xAxisUnits, unknownXAxisValuesTextTranslated, err := attributeDisplay.GetProfileAttributeDisplayInfo(xAxisAttributeName) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } xAxisLabel := getBoldLabelCentered("X-Axis") changeXAxisAttributeButton := widget.NewButton(xAxisAttributeTitle, func(){ currentPageWithNewAttributeFunction := func(newXAxisAttributeName string){ setViewUserAttributeStatisticsPage_BarChart(window, identityType, newXAxisAttributeName, yAxisAttribute, yAxisUnits, showYAxisPercentage, false, false, nil, false, nil, nil, previousPage) } setViewUserAttributeStatistics_ChooseXAxisAttributePage(window, identityType, currentPageWithNewAttributeFunction, currentPage) }) xAxisColumn := container.NewVBox(xAxisLabel, changeXAxisAttributeButton) yAxisLabel := getBoldLabelCentered("Y-Axis") changeYAxisAttributeButton := widget.NewButton(translate(yAxisAttribute), func(){ currentPageWithNewAttributeFunction := func(newYAxisAttribute string, newYAxisUnits string, showYAxisPercentage bool){ setViewUserAttributeStatisticsPage_BarChart(window, identityType, xAxisAttributeName, newYAxisAttribute, newYAxisUnits, showYAxisPercentage, false, false, nil, false, nil, nil, previousPage) } setViewUserAttributeStatistics_ChooseYAxisAttributePage(window, identityType, currentPageWithNewAttributeFunction, currentPage) }) yAxisColumn := container.NewVBox(yAxisLabel, changeYAxisAttributeButton) chartSettingsRow := container.NewHBox(layout.NewSpacer(), widget.NewSeparator(), identityTypeColumn, widget.NewSeparator(), chartTypeColumn, widget.NewSeparator(), xAxisColumn, widget.NewSeparator(), yAxisColumn, widget.NewSeparator(), layout.NewSpacer()) if (statisticsReady == true){ if (anyUsersExist == false){ description1 := getBoldLabelCentered("No " + identityType + " profiles exist.") description2 := getLabelCentered("No statistics are available to show.") description3 := getLabelCentered("You must wait for profiles to be downloaded.") page := container.NewVBox(title, backButton, widget.NewSeparator(), chartSettingsRow, widget.NewSeparator(), description1, description2, description3) setPageContent(page, window) return } viewDataButton := widget.NewButtonWithIcon("View Data", theme.ListIcon(), func(){ setViewUserStatisticsDataPage(window, xAxisAttributeTitle, yAxisAttribute, showYAxisPercentage, statisticsDatumsList, groupingPerformed, groupedStatisticsDatumsList, true, xAxisUnits, yAxisUnits, currentPage) }) viewFullscreenButton := widget.NewButtonWithIcon("View Fullscreen", theme.ZoomInIcon(), func(){ setViewFullpageImagePage(window, chartImage, currentPage) }) warningButton := widget.NewButtonWithIcon("Warning", theme.WarningIcon(), func(){ setNetworkStatisticsInaccuracyWarningPage(window, currentPage) }) chartCompleteButtonsRow := getContainerCentered(container.NewGridWithRows(1, viewDataButton, viewFullscreenButton, warningButton)) header := container.NewVBox(title, backButton, widget.NewSeparator(), chartSettingsRow, widget.NewSeparator(), chartCompleteButtonsRow, widget.NewSeparator()) chartFyneImage := canvas.NewImageFromImage(chartImage) chartFyneImage.FillMode = canvas.ImageFillContain page := container.NewBorder(header, nil, nil, nil, chartFyneImage) setPageContent(page, window) return } loadingTextBinding := binding.NewString() loadingTextLabel := widget.NewLabelWithData(loadingTextBinding) loadingTextLabel.TextStyle = getFyneTextStyle_Bold() loadingTextLabelCentered := container.NewCenter(loadingTextLabel) appNetworkType, err := getAppNetworkType.GetAppNetworkType() if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } createChartAndRefreshPageFunction := func(){ var functionCompleteBoolMutex sync.RWMutex functionCompleteBool := false updateLoadingBindingFunction := func(){ secondsElapsed := 0 for{ if (secondsElapsed%3 == 0){ loadingTextBinding.Set("Loading Statistics.") } else if (secondsElapsed%3 == 1){ loadingTextBinding.Set("Loading Statistics..") } else { loadingTextBinding.Set("Loading Statistics...") } pageHasChanged := checkIfPageHasChangedFunction() if (pageHasChanged == true){ return } functionCompleteBoolMutex.RLock() functionIsComplete := functionCompleteBool functionCompleteBoolMutex.RUnlock() if (functionIsComplete == true){ return } secondsElapsed += 1 time.Sleep(time.Second) } } go updateLoadingBindingFunction() totalAnalyzedUsers, statisticsDatumsList, groupingPerformed, groupedStatisticsDatumsList, formatYAxisValuesFunction, err := userStatistics.GetUserStatisticsDatumsLists_BarChart(identityType, appNetworkType, xAxisAttributeName, xAxisIsNumerical, formatXAxisValuesFunction, unknownXAxisValuesTextTranslated, yAxisAttribute) if (err != nil) { functionCompleteBoolMutex.Lock() functionCompleteBool = true functionCompleteBoolMutex.Unlock() pageHasChanged := checkIfPageHasChangedFunction() if (pageHasChanged == false){ setErrorEncounteredPage(window, err, previousPage) } return } if (len(statisticsDatumsList) == 0){ functionCompleteBoolMutex.Lock() functionCompleteBool = true functionCompleteBoolMutex.Unlock() pageHasChanged := checkIfPageHasChangedFunction() if (pageHasChanged == false){ setViewUserAttributeStatisticsPage_BarChart(window, identityType, xAxisAttributeName, yAxisAttribute, yAxisUnits, showYAxisPercentage, true, false, nil, false, nil, nil, previousPage) } return } getChartStatisticsDatumsList := func()[]statisticsDatum.StatisticsDatum{ if (groupingPerformed == false){ return statisticsDatumsList } return groupedStatisticsDatumsList } chartStatisticsDatumsList := getChartStatisticsDatumsList() getChartTitle := func()string{ totalAnalyzedUsersString := helpers.ConvertIntToString(totalAnalyzedUsers) chartTitle := identityType + " Statistics: " + xAxisAttributeTitle if (xAxisUnits != ""){ xAxisUnitsTrimmed := strings.TrimSpace(xAxisUnits) chartTitle += " (" + xAxisUnitsTrimmed + ")" } chartTitle += " by " + yAxisAttribute if (yAxisUnits != "" && yAxisUnits != translate(" users")){ yAxisUnitsTrimmed := strings.TrimSpace(yAxisUnits) chartTitle += " (" + yAxisUnitsTrimmed + ")" } chartTitle += " - " + totalAnalyzedUsersString + " Users" return chartTitle } chartTitle := getChartTitle() newChartImage, err := createCharts.CreateBarChart(chartTitle, chartStatisticsDatumsList, formatYAxisValuesFunction, true, yAxisUnits) if (err != nil) { functionCompleteBoolMutex.Lock() functionCompleteBool = true functionCompleteBoolMutex.Unlock() pageHasChanged := checkIfPageHasChangedFunction() if (pageHasChanged == false){ setErrorEncounteredPage(window, err, previousPage) } return } functionCompleteBoolMutex.Lock() functionCompleteBool = true functionCompleteBoolMutex.Unlock() pageHasChanged := checkIfPageHasChangedFunction() if (pageHasChanged == false){ setViewUserAttributeStatisticsPage_BarChart(window, identityType, xAxisAttributeName, yAxisAttribute, yAxisUnits, showYAxisPercentage, true, true, statisticsDatumsList, groupingPerformed, groupedStatisticsDatumsList, newChartImage, previousPage) } } header := container.NewVBox(title, backButton, widget.NewSeparator(), chartSettingsRow, widget.NewSeparator()) page := container.NewBorder(header, nil, nil, nil, loadingTextLabelCentered) setPageContent(page, window) go createChartAndRefreshPageFunction() } func setViewUserAttributeStatisticsPage_DonutChart( window fyne.Window, identityType string, attributeName string, statisticsReady bool, anyUsersExist bool, statisticsDatumsList []statisticsDatum.StatisticsDatum, groupingPerformed bool, groupedStatisticsDatumsList []statisticsDatum.StatisticsDatum, chartImage image.Image, previousPage func()){ currentPage := func(){setViewUserAttributeStatisticsPage_DonutChart(window, identityType, attributeName, statisticsReady, anyUsersExist, statisticsDatumsList, groupingPerformed, groupedStatisticsDatumsList, chartImage, previousPage)} pageIdentifier, err := helpers.GetNewRandomHexString(16) if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } appMemory.SetMemoryEntry("CurrentViewedPage", pageIdentifier) checkIfPageHasChangedFunction := func()bool{ exists, currentViewedPage := appMemory.GetMemoryEntry("CurrentViewedPage") if (exists == true && currentViewedPage == pageIdentifier){ return false } return true } title := getPageTitleCentered("User Attribute Statistics - Donut Chart") backButton := getBackButtonCentered(previousPage) identityTypeLabel := getBoldLabelCentered("Identity Type") handleIdentityTypeSelectFunction := func(newIdentityType string){ if (newIdentityType == identityType){ return } if (newIdentityType == "Mate"){ setViewUserAttributeStatisticsPage_DonutChart(window, "Mate", "Age", false, false, nil, false, nil, nil, previousPage) return } if (newIdentityType == "Host"){ setViewUserAttributeStatisticsPage_DonutChart(window, "Host", "SeekiaVersion", false, false, nil, false, nil, nil, previousPage) return } // newIdentityType == "Moderator" setViewUserAttributeStatisticsPage_DonutChart(window, "Moderator", "IdentityScore", false, false, nil, false, nil, nil, previousPage) } identityTypesList := []string{"Mate", "Host", "Moderator"} identityTypeSelector := widget.NewSelect(identityTypesList, handleIdentityTypeSelectFunction) identityTypeSelector.Selected = identityType identityTypeColumn := container.NewVBox(identityTypeLabel, identityTypeSelector) chartTypeLabel := getBoldLabelCentered("Chart Type:") chartTypesList := []string{"Bar Chart", "Donut Chart"} handleSelectFunction := func(newType string){ if (newType == "Donut Chart"){ return } if (newType == "Bar Chart"){ setViewUserAttributeStatisticsPage_BarChart(window, identityType, attributeName, "Number Of Users", " Users", true, false, false, nil, false, nil, nil, previousPage) } } chartTypeSelector := widget.NewSelect(chartTypesList, handleSelectFunction) chartTypeSelector.Selected = "Donut Chart" chartTypeColumn := container.NewVBox(chartTypeLabel, chartTypeSelector) attributeTitle, attributeIsNumerical, formatAttributeValuesFunction, attributeUnits, unknownValuesTextTranslated, err := attributeDisplay.GetProfileAttributeDisplayInfo(attributeName) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } attributeLabel := getBoldLabelCentered("Attribute:") changeAttributeButton := widget.NewButton(attributeTitle, func(){ currentPageWithNewAttributeFunction := func(newAttributeName string){ setViewUserAttributeStatisticsPage_DonutChart(window, identityType, newAttributeName, false, false, nil, false, nil, nil, previousPage) } setViewUserAttributeStatistics_ChooseXAxisAttributePage(window, identityType, currentPageWithNewAttributeFunction, currentPage) }) attributeColumn := container.NewVBox(attributeLabel, changeAttributeButton) chartSettingsRow := container.NewHBox(layout.NewSpacer(), widget.NewSeparator(), identityTypeColumn, widget.NewSeparator(), chartTypeColumn, widget.NewSeparator(), attributeColumn, widget.NewSeparator(), layout.NewSpacer()) if (statisticsReady == true){ if (anyUsersExist == false){ description1 := getBoldLabelCentered("No " + identityType + " profiles exist.") description2 := getLabelCentered("No statistics are available to show.") description3 := getLabelCentered("You must wait for profiles to be downloaded.") page := container.NewVBox(title, backButton, widget.NewSeparator(), chartSettingsRow, widget.NewSeparator(), description1, description2, description3) setPageContent(page, window) return } viewDataButton := widget.NewButtonWithIcon("View Data", theme.ListIcon(), func(){ setViewUserStatisticsDataPage(window, attributeTitle, "Number Of Users", true, statisticsDatumsList, groupingPerformed, groupedStatisticsDatumsList, true, attributeUnits, "Users", currentPage) }) viewFullscreenButton := widget.NewButtonWithIcon("View Fullscreen", theme.ZoomInIcon(), func(){ setViewFullpageImagePage(window, chartImage, currentPage) }) warningButton := widget.NewButtonWithIcon("Warning", theme.WarningIcon(), func(){ setNetworkStatisticsInaccuracyWarningPage(window, currentPage) }) chartCompleteButtonsRow := getContainerCentered(container.NewGridWithRows(1, viewDataButton, viewFullscreenButton, warningButton)) header := container.NewVBox(title, backButton, widget.NewSeparator(), chartSettingsRow, widget.NewSeparator(), chartCompleteButtonsRow, widget.NewSeparator()) chartFyneImage := canvas.NewImageFromImage(chartImage) chartFyneImage.FillMode = canvas.ImageFillContain page := container.NewBorder(header, nil, nil, nil, chartFyneImage) setPageContent(page, window) return } loadingTextBinding := binding.NewString() loadingTextLabel := widget.NewLabelWithData(loadingTextBinding) loadingTextLabel.TextStyle = getFyneTextStyle_Bold() loadingTextLabelCentered := container.NewCenter(loadingTextLabel) appNetworkType, err := getAppNetworkType.GetAppNetworkType() if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } createChartAndRefreshPageFunction := func(){ var functionCompleteBoolMutex sync.RWMutex functionCompleteBool := false updateLoadingBindingFunction := func(){ secondsElapsed := 0 for{ if (secondsElapsed%3 == 0){ loadingTextBinding.Set("Loading Statistics.") } else if (secondsElapsed%3 == 1){ loadingTextBinding.Set("Loading Statistics..") } else { loadingTextBinding.Set("Loading Statistics...") } pageHasChanged := checkIfPageHasChangedFunction() if (pageHasChanged == true){ return } functionCompleteBoolMutex.RLock() functionIsComplete := functionCompleteBool functionCompleteBoolMutex.RUnlock() if (functionIsComplete == true){ return } secondsElapsed += 1 time.Sleep(time.Second) } } go updateLoadingBindingFunction() totalAnalyzedUsers, statisticsDatumsList, groupingPerformed, groupedStatisticsDatumsList, err := userStatistics.GetUserStatisticsDatumsLists_DonutChart(identityType, appNetworkType, attributeName, attributeIsNumerical, formatAttributeValuesFunction, unknownValuesTextTranslated) if (err != nil) { functionCompleteBoolMutex.Lock() functionCompleteBool = true functionCompleteBoolMutex.Unlock() pageHasChanged := checkIfPageHasChangedFunction() if (pageHasChanged == false){ setErrorEncounteredPage(window, err, previousPage) } return } if (len(statisticsDatumsList) == 0){ functionCompleteBoolMutex.Lock() functionCompleteBool = true functionCompleteBoolMutex.Unlock() pageHasChanged := checkIfPageHasChangedFunction() if (pageHasChanged == false){ setViewUserAttributeStatisticsPage_DonutChart(window, identityType, attributeName, true, false, nil, false, nil, nil, previousPage) } return } getChartStatisticsDatumsList := func()[]statisticsDatum.StatisticsDatum{ if (groupingPerformed == false){ return statisticsDatumsList } return groupedStatisticsDatumsList } chartStatisticsDatumsList := getChartStatisticsDatumsList() getChartTitle := func()string{ totalAnalyzedUsersString := helpers.ConvertIntToString(totalAnalyzedUsers) chartTitle := identityType + " Statistics: " + attributeTitle if (attributeUnits != ""){ attributeUnitsTrimmed := strings.TrimPrefix(attributeUnits, " ") chartTitle += " (" + attributeUnitsTrimmed + ")" } chartTitle += " - " + totalAnalyzedUsersString + " Users" return chartTitle } chartTitle := getChartTitle() newChartImage, err := createCharts.CreateDonutChart(chartTitle, chartStatisticsDatumsList) if (err != nil){ functionCompleteBoolMutex.Lock() functionCompleteBool = true functionCompleteBoolMutex.Unlock() pageHasChanged := checkIfPageHasChangedFunction() if (pageHasChanged == false){ setErrorEncounteredPage(window, err, previousPage) } return } functionCompleteBoolMutex.Lock() functionCompleteBool = true functionCompleteBoolMutex.Unlock() pageHasChanged := checkIfPageHasChangedFunction() if (pageHasChanged == false){ setViewUserAttributeStatisticsPage_DonutChart(window, identityType, attributeName, true, true, statisticsDatumsList, groupingPerformed, groupedStatisticsDatumsList, newChartImage, previousPage) } } header := container.NewVBox(title, backButton, widget.NewSeparator(), chartSettingsRow, widget.NewSeparator()) page := container.NewBorder(header, nil, nil, nil, loadingTextLabelCentered) setPageContent(page, window) go createChartAndRefreshPageFunction() } //Inputs: // -fyne.Window // -string: Profile Type // -func(attributeName string) // -func(): Previous page func setViewUserAttributeStatistics_ChooseXAxisAttributePage(window fyne.Window, profileType string, viewStatisticsPageWithNewAttribute func(string), previousPage func()){ title := getPageTitleCentered(translate("Choose Attribute")) backButton := getBackButtonCentered(previousPage) attributesToShowList := make([]string, 0) addAttributeToListFunction := func(attributeName string){ attributesToShowList = append(attributesToShowList, attributeName) } if (profileType == "Mate"){ addAttributeToListFunction("Age") addAttributeToListFunction("Height") addAttributeToListFunction("Sex") addAttributeToListFunction("Sexuality") addAttributeToListFunction("Distance") addAttributeToListFunction("WealthInGold") } addAttributeToListFunction("ProfileLanguage") if (profileType == "Mate" || profileType == "Moderator"){ addAttributeToListFunction("HasMessagedMe") addAttributeToListFunction("IHaveMessaged") } if (profileType == "Mate"){ addAttributeToListFunction("HasRejectedMe") addAttributeToListFunction("IsLiked") addAttributeToListFunction("IsIgnored") } addAttributeToListFunction("IsMyContact") if (profileType == "Mate"){ addAttributeToListFunction("PrimaryLocationCountry") addAttributeToListFunction("BodyFat") addAttributeToListFunction("BodyMuscle") addAttributeToListFunction("HairColor") addAttributeToListFunction("HairTexture") addAttributeToListFunction("EyeColor") addAttributeToListFunction("SkinColor") addAttributeToListFunction("RacialSimilarity") addAttributeToListFunction("EyeColorSimilarity") addAttributeToListFunction("EyeColorGenesSimilarity") addAttributeToListFunction("HairColorSimilarity") addAttributeToListFunction("HairColorGenesSimilarity") addAttributeToListFunction("SkinColorSimilarity") addAttributeToListFunction("SkinColorGenesSimilarity") addAttributeToListFunction("HairTextureSimilarity") addAttributeToListFunction("HairTextureGenesSimilarity") addAttributeToListFunction("FacialStructureGenesSimilarity") addAttributeToListFunction("23andMe_AncestralSimilarity") addAttributeToListFunction("23andMe_MaternalHaplogroupSimilarity") addAttributeToListFunction("23andMe_PaternalHaplogroupSimilarity") addAttributeToListFunction("HasHIV") addAttributeToListFunction("HasGenitalHerpes") addAttributeToListFunction("GenderIdentity") addAttributeToListFunction("23andMe_MaternalHaplogroup") addAttributeToListFunction("23andMe_PaternalHaplogroup") addAttributeToListFunction("23andMe_NeanderthalVariants") addAttributeToListFunction("FruitRating") addAttributeToListFunction("VegetablesRating") addAttributeToListFunction("NutsRating") addAttributeToListFunction("GrainsRating") addAttributeToListFunction("DairyRating") addAttributeToListFunction("SeafoodRating") addAttributeToListFunction("BeefRating") addAttributeToListFunction("PorkRating") addAttributeToListFunction("PoultryRating") addAttributeToListFunction("EggsRating") addAttributeToListFunction("BeansRating") addAttributeToListFunction("Fame") addAttributeToListFunction("AlcoholFrequency") addAttributeToListFunction("TobaccoFrequency") addAttributeToListFunction("CannabisFrequency") addAttributeToListFunction("PetsRating") addAttributeToListFunction("DogsRating") addAttributeToListFunction("CatsRating") addAttributeToListFunction("OffspringProbabilityOfAnyMonogenicDisease") addAttributeToListFunction("TotalPolygenicDiseaseRiskScore") addAttributeToListFunction("OffspringTotalPolygenicDiseaseRiskScore") } buttonsGrid := container.NewGridWithColumns(1) for _, attributeName := range attributesToShowList{ attributeTitle, _, _, _, _, err := attributeDisplay.GetProfileAttributeDisplayInfo(attributeName) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } attributeButton := widget.NewButton(attributeTitle, func(){ viewStatisticsPageWithNewAttribute(attributeName) }) buttonsGrid.Add(attributeButton) } buttonsGridCentered := getContainerCentered(buttonsGrid) page := container.NewVBox(title, backButton, widget.NewSeparator(), buttonsGridCentered) setPageContent(page, window) } //Inputs: // -fyne.Window // -string: Profile type // -func(attributeName string, yAxisUnits string, showYAxisPercentage bool) // -func(): Previous page func setViewUserAttributeStatistics_ChooseYAxisAttributePage(window fyne.Window, profileType string, viewStatisticsPageWithNewAttribute func(string, string, bool), previousPage func()){ title := getPageTitleCentered("Choose Y-Axis Attribute") backButton := getBackButtonCentered(previousPage) numberOfUsersButton := widget.NewButton("Number Of Users", func(){ viewStatisticsPageWithNewAttribute("Number Of Users", translate(" Users"), true) }) buttonsGrid := container.NewGridWithColumns(1, numberOfUsersButton) if (profileType == "Mate"){ averageHeightButton := widget.NewButton("Average Height", func(){ viewStatisticsPageWithNewAttribute("Average Height", translate(" Centimeters"), false) }) buttonsGrid.Add(averageHeightButton) averageAgeButton := widget.NewButton("Average Age", func(){ viewStatisticsPageWithNewAttribute("Average Age", translate(" Years Old"), false) }) buttonsGrid.Add(averageAgeButton) getAppCurrency := func()(string, error){ exists, appCurrencyCode, err := globalSettings.GetSetting("Currency") if (err != nil) { return "", err } if (exists == false){ return "USD", nil } return appCurrencyCode, nil } appCurrencyCode, err := getAppCurrency() if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } averageWealthButton := widget.NewButton("Average Wealth", func(){ wealthUnits := " " + appCurrencyCode viewStatisticsPageWithNewAttribute("Average Wealth", wealthUnits, false) }) buttonsGrid.Add(averageWealthButton) averageNeanderthalVariantsButton := widget.NewButton("Average 23andMe Neanderthal Variants", func(){ viewStatisticsPageWithNewAttribute("Average 23andMe Neanderthal Variants", translate(" Variants"), false) }) buttonsGrid.Add(averageNeanderthalVariantsButton) averageBodyFatButton := widget.NewButton("Average Body Fat", func(){ viewStatisticsPageWithNewAttribute("Average Body Fat", "/4", false) }) buttonsGrid.Add(averageBodyFatButton) averageBodyMuscleButton := widget.NewButton("Average Body Muscle", func(){ viewStatisticsPageWithNewAttribute("Average Body Muscle", "/4", false) }) buttonsGrid.Add(averageBodyMuscleButton) oneToTenValueAttributesList := []string{ "Fruit Rating", "Vegetables Rating", "Nuts Rating", "Grains Rating", "Dairy Rating", "Seafood Rating", "Beef Rating", "Pork Rating", "Poultry Rating", "Eggs Rating", "Beans Rating", "Fame", "Alcohol Frequency", "Tobacco Frequency", "Cannabis Frequency", "Pets Rating", "Dogs Rating", "Cats Rating", } for _, attributeTitle := range oneToTenValueAttributesList{ averageAttributeTitle := "Average " + attributeTitle averageAttributeButton := widget.NewButton(averageAttributeTitle, func(){ viewStatisticsPageWithNewAttribute(averageAttributeTitle, "/10", false) }) buttonsGrid.Add(averageAttributeButton) } } buttonsGridCentered := getContainerCentered(buttonsGrid) page := container.NewVBox(title, backButton, widget.NewSeparator(), buttonsGridCentered) setPageContent(page, window) } func setViewUserStatisticsDataPage( window fyne.Window, attributeTitle string, rightColumnName string, showPercentageColumn bool, statisticsDatumsList []statisticsDatum.StatisticsDatum, groupingPerformed bool, groupedStatisticsDatumsList []statisticsDatum.StatisticsDatum, showGroupedStatistics bool, attributeColumnUnits string, rightColumnUnits string, previousPage func()){ setLoadingScreen(window, "User Statistics Data", "Loading statistics data...") title := getPageTitleCentered("User Statistics Data") backButton := getBackButtonCentered(previousPage) header := container.NewVBox(title, backButton, widget.NewSeparator()) if (groupingPerformed == true){ if (showGroupedStatistics == false){ showDataGroupedButton := getWidgetCentered(widget.NewButton("Show Data Grouped", func(){ setViewUserStatisticsDataPage(window, attributeTitle, rightColumnName, showPercentageColumn, statisticsDatumsList, true, groupedStatisticsDatumsList, true, attributeColumnUnits, rightColumnUnits, previousPage) })) header.Add(showDataGroupedButton) } else { showDataRawButton := getWidgetCentered(widget.NewButton("Show Data Raw", func(){ setViewUserStatisticsDataPage(window, attributeTitle, rightColumnName, showPercentageColumn, statisticsDatumsList, true, groupedStatisticsDatumsList, false, attributeColumnUnits, rightColumnUnits, previousPage) })) header.Add(showDataRawButton) } header.Add(widget.NewSeparator()) } getStatisticsDatumsListToShow := func()[]statisticsDatum.StatisticsDatum{ if (groupingPerformed == true && showGroupedStatistics == true){ return groupedStatisticsDatumsList } return statisticsDatumsList } statisticsDatumsToShowList := getStatisticsDatumsListToShow() getStatisticsDataGrid := func()(*fyne.Container, error){ allValuesSummed := float64(0) if (showPercentageColumn == true){ for _, datum := range statisticsDatumsToShowList{ datumValue := datum.Value allValuesSummed += datumValue } } attributeColumnValuesList := make([]string, 0, len(statisticsDatumsToShowList)) rightColumnValuesList := make([]string, 0, len(statisticsDatumsToShowList)) percentageColumnValuesList := make([]string, 0, len(statisticsDatumsToShowList)) for _, datum := range statisticsDatumsToShowList{ // Each datum is represents a row in the data grid. datumLabelFormatted := datum.LabelFormatted datumValueFormatted := datum.ValueFormatted attributeColumnValuesList = append(attributeColumnValuesList, datumLabelFormatted) rightColumnValuesList = append(rightColumnValuesList, datumValueFormatted) if (showPercentageColumn == true){ datumValue := datum.Value getValuePercentage := func()float64{ if (allValuesSummed == 0){ return 0 } valuePercentage := (datumValue/allValuesSummed)*100 return valuePercentage } valuePercentage := getValuePercentage() valuePercentageString := helpers.ConvertFloat64ToStringRounded(valuePercentage, 2) valuePercentageFormatted := valuePercentageString + "%" percentageColumnValuesList = append(percentageColumnValuesList, valuePercentageFormatted) } } getAttributeColumnTitle := func()string{ if (attributeColumnUnits != ""){ attributeColumnUnitsTrimmed := strings.TrimSpace(attributeColumnUnits) attributeColumnTitle := attributeTitle + " (" + attributeColumnUnitsTrimmed + ")" return attributeColumnTitle } return attributeTitle } attributeColumnTitle := getAttributeColumnTitle() getRightColumnTitle := func()string{ if (rightColumnUnits != ""){ rightColumnUnitsTrimmed := strings.TrimSpace(rightColumnUnits) rightColumnTitle := rightColumnName + " (" + rightColumnUnitsTrimmed + ")" return rightColumnTitle } return rightColumnName } rightColumnTitle := getRightColumnTitle() if (showGroupedStatistics == false){ // Raw statistics must be rendered using a widget list // Otherwise, rendering thousands of rows is too difficult for fyne headerText := attributeColumnTitle + " - " + rightColumnTitle if (showPercentageColumn == true){ headerText += " - Percentage" } headerLabel := getBoldLabelCentered(headerText) rowValuesList := make([]string, 0, len(attributeColumnValuesList)) for index, attributeColumnValue := range attributeColumnValuesList{ rightColumnValue := rightColumnValuesList[index] if (showPercentageColumn == false){ rowValue := attributeColumnValue + " - " + rightColumnValue rowValuesList = append(rowValuesList, rowValue) } else { percentageColumnValue := percentageColumnValuesList[index] rowValue := attributeColumnValue + " - " + rightColumnValue + " - " + percentageColumnValue rowValuesList = append(rowValuesList, rowValue) } } onClickedFunction := func(_ int){} widgetList, err := getFyneWidgetListFromStringList(rowValuesList, onClickedFunction) if (err != nil) { return nil, err } statisticsDataGrid := container.NewBorder(headerLabel, nil, nil, nil, widgetList) return statisticsDataGrid, nil } attributeColumnHeader := getItalicLabelCentered(attributeColumnTitle) rightColumnHeader := getItalicLabelCentered(rightColumnTitle) percentageHeader := getItalicLabelCentered("Percentage") attributeColumn := container.NewVBox(attributeColumnHeader, widget.NewSeparator()) rightColumn := container.NewVBox(rightColumnHeader, widget.NewSeparator()) percentageColumn := container.NewVBox(percentageHeader, widget.NewSeparator()) for index, attributeColumnValue := range attributeColumnValuesList{ rightColumnValue := rightColumnValuesList[index] attributeColumnLabel := getBoldLabelCentered(attributeColumnValue) rightColumnLabel := getBoldLabelCentered(rightColumnValue) attributeColumn.Add(attributeColumnLabel) rightColumn.Add(rightColumnLabel) if (showPercentageColumn == true){ percentageColumnValue := percentageColumnValuesList[index] valuePercentageLabel := getBoldLabelCentered(percentageColumnValue) percentageColumn.Add(valuePercentageLabel) } } if (showPercentageColumn == true){ statisticsDataGrid := container.NewHBox(layout.NewSpacer(), attributeColumn, rightColumn, percentageColumn, layout.NewSpacer()) return statisticsDataGrid, nil } statisticsDataGrid := container.NewHBox(layout.NewSpacer(), attributeColumn, rightColumn, layout.NewSpacer()) return statisticsDataGrid, nil } statisticsDataGrid, err := getStatisticsDataGrid() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } page := container.NewBorder(header, nil, nil, nil, statisticsDataGrid) setPageContent(page, window) }