solarwindpy 0.0.1.dev0__py3-none-any.whl → 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of solarwindpy might be problematic. Click here for more details.
- plans/.velocity/metrics.json +96 -0
- plans/0-overview-template.md +268 -0
- plans/N-phase-template.md +106 -0
- plans/PLAN_AUDIT_SUMMARY.md +173 -0
- plans/TEMPLATE-USAGE-GUIDE.md +198 -0
- plans/__init__.py +1 -0
- plans/abandoned/compaction-agent-system/0-Overview.md +123 -0
- plans/abandoned/compaction-agent-system/agents-index-update-plan.md +109 -0
- plans/abandoned/compaction-agent-system/compacted_state.md +85 -0
- plans/abandoned/compaction-agent-system/implementation-plan.md +107 -0
- plans/abandoned/compaction-agent-system/system-validation-report.md +159 -0
- plans/abandoned/compaction-agent-system/usage-guide.md +210 -0
- plans/abandoned/hook-system-enhancement/0-Overview.md +214 -0
- plans/abandoned/hook-system-enhancement/1-Phase1-Core-Infrastructure.md +313 -0
- plans/abandoned/hook-system-enhancement/2-Phase2-Intelligent-Testing.md +385 -0
- plans/abandoned/hook-system-enhancement/3-Phase3-Physics-Validation.md +444 -0
- plans/abandoned/hook-system-enhancement/4-Phase4-Performance-Monitoring.md +458 -0
- plans/abandoned/hook-system-enhancement/5-Phase5-Developer-Experience.md +532 -0
- plans/abandoned/hook-system-enhancement/6-Implementation-Timeline.md +274 -0
- plans/abandoned/hook-system-enhancement/7-Risk-Management.md +376 -0
- plans/abandoned/hook-system-enhancement/8-Testing-Strategy.md +579 -0
- plans/abandoned/readthedocs-automation/0-Overview.md +247 -0
- plans/abandoned/readthedocs-automation/1-Emergency-Documentation-Fixes.md +270 -0
- plans/abandoned/readthedocs-automation/2-Template-System-Enhancement.md +811 -0
- plans/abandoned/readthedocs-automation/3-Quality-Audit-ReadTheDocs-Integration.md +844 -0
- plans/abandoned/readthedocs-automation/4-Plan-Consolidation-Cleanup.md +632 -0
- plans/abandoned/readthedocs-automation/9-Closeout.md +207 -0
- plans/abandoned/readthedocs-automation/ABANDONMENT_REASON.md +72 -0
- plans/cicd-architecture-redesign/0-Overview.md +193 -0
- plans/cicd-architecture-redesign/1-Workflow-Creation.md +103 -0
- plans/cicd-architecture-redesign/2-Version-Detection.md +123 -0
- plans/cicd-architecture-redesign/3-Deployment-Gates.md +169 -0
- plans/cicd-architecture-redesign/4-RC-Testing.md +194 -0
- plans/cicd-architecture-redesign/5-TestPyPI-Validation.md +264 -0
- plans/cicd-architecture-redesign/6-Production-Release.md +263 -0
- plans/cicd-architecture-redesign/7-Cleanup.md +243 -0
- plans/cicd-architecture-redesign/8-Documentation.md +285 -0
- plans/cicd-architecture-redesign/Closeout.md +225 -0
- plans/closeout-template.md +259 -0
- plans/completed/circular-import-audit/0-Overview.md +152 -0
- plans/completed/circular-import-audit/1-Static-Dependency-Analysis.md +62 -0
- plans/completed/circular-import-audit/2-Dynamic-Import-Testing.md +56 -0
- plans/completed/circular-import-audit/3-Performance-Impact-Assessment.md +56 -0
- plans/completed/circular-import-audit/4-Issue-Remediation.md +78 -0
- plans/completed/circular-import-audit/5-Preventive-Infrastructure.md +89 -0
- plans/completed/claude-settings-ecosystem-alignment/0-Overview.md +162 -0
- plans/completed/claude-settings-ecosystem-alignment/1-Security-Foundation.md +148 -0
- plans/completed/claude-settings-ecosystem-alignment/2-Hook-Integration.md +158 -0
- plans/completed/claude-settings-ecosystem-alignment/3-Agent-System-Integration.md +177 -0
- plans/completed/claude-settings-ecosystem-alignment/4-Enhanced-Workflow-Automation.md +159 -0
- plans/completed/claude-settings-ecosystem-alignment/5-Validation-Monitoring.md +181 -0
- plans/completed/claude-settings-ecosystem-alignment/compacted_session_state.md +290 -0
- plans/completed/combined_plan_with_checklist_documentation/1-Overview-and-Goals.md +51 -0
- plans/completed/combined_plan_with_checklist_documentation/2-Toolchain-and-Hosting.md +69 -0
- plans/completed/combined_plan_with_checklist_documentation/3-Repository-Structure.md +61 -0
- plans/completed/combined_plan_with_checklist_documentation/4-Configuration-and-Standards.md +70 -0
- plans/completed/combined_plan_with_checklist_documentation/5-Documentation-Content.md +62 -0
- plans/completed/combined_plan_with_checklist_documentation/6-CI-CD-and-Validation.md +58 -0
- plans/completed/combined_plan_with_checklist_documentation/7-Maintenance.md +55 -0
- plans/completed/combined_test_plan_with_checklist_fitfunctions/0-Overview.md +135 -0
- plans/completed/combined_test_plan_with_checklist_fitfunctions/1-Common-fixtures.md +59 -0
- plans/completed/combined_test_plan_with_checklist_fitfunctions/10-power_laws.md +56 -0
- plans/completed/combined_test_plan_with_checklist_fitfunctions/2-core.py-FitFunction.md +118 -0
- plans/completed/combined_test_plan_with_checklist_fitfunctions/3-gaussians.py-Gaussian-GaussianNormalized-GaussianLn.md +69 -0
- plans/completed/combined_test_plan_with_checklist_fitfunctions/4-trend_fits.py-TrendFit.md +99 -0
- plans/completed/combined_test_plan_with_checklist_fitfunctions/5-plots.py-FFPlot.md +98 -0
- plans/completed/combined_test_plan_with_checklist_fitfunctions/6-tex_info.py-TeXinfo.md +79 -0
- plans/completed/combined_test_plan_with_checklist_fitfunctions/7-Justification.md +49 -0
- plans/completed/combined_test_plan_with_checklist_fitfunctions/8-exponentials.md +64 -0
- plans/completed/combined_test_plan_with_checklist_fitfunctions/9-lines.md +58 -0
- plans/completed/combined_test_plan_with_checklist_plotting/0-Overview.md +142 -0
- plans/completed/combined_test_plan_with_checklist_plotting/1-base.py.md +90 -0
- plans/completed/combined_test_plan_with_checklist_plotting/10-labels-special.py.md +102 -0
- plans/completed/combined_test_plan_with_checklist_plotting/11-labels-chemistry.py.md +212 -0
- plans/completed/combined_test_plan_with_checklist_plotting/12-labels-composition.py.md +242 -0
- plans/completed/combined_test_plan_with_checklist_plotting/13-labels-datetime.py.md +247 -0
- plans/completed/combined_test_plan_with_checklist_plotting/14-labels-elemental_abundance.py.md +274 -0
- plans/completed/combined_test_plan_with_checklist_plotting/15-visual-validation.md +256 -0
- plans/completed/combined_test_plan_with_checklist_plotting/16-integration-testing.md +266 -0
- plans/completed/combined_test_plan_with_checklist_plotting/17-performance-benchmarks.md +267 -0
- plans/completed/combined_test_plan_with_checklist_plotting/18-Fixtures-and-Utilities.md +86 -0
- plans/completed/combined_test_plan_with_checklist_plotting/2-agg_plot.py.md +90 -0
- plans/completed/combined_test_plan_with_checklist_plotting/3-histograms.py.md +201 -0
- plans/completed/combined_test_plan_with_checklist_plotting/4-scatter.py.md +167 -0
- plans/completed/combined_test_plan_with_checklist_plotting/5-spiral.py.md +216 -0
- plans/completed/combined_test_plan_with_checklist_plotting/6-orbits.py.md +108 -0
- plans/completed/combined_test_plan_with_checklist_plotting/7-tools.py.md +86 -0
- plans/completed/combined_test_plan_with_checklist_plotting/8-select_data_from_figure.py.md +97 -0
- plans/completed/combined_test_plan_with_checklist_plotting/9-labels-base.py.md +88 -0
- plans/completed/combined_test_plan_with_checklist_solar_activity/.gitkeep +0 -0
- plans/completed/combined_test_plan_with_checklist_solar_activity/0-Overview.md +170 -0
- plans/completed/combined_test_plan_with_checklist_solar_activity/1-Package-Entry-Point-__init__.py.md +121 -0
- plans/completed/combined_test_plan_with_checklist_solar_activity/2-Core-Base-Classes-base.py.md +142 -0
- plans/completed/combined_test_plan_with_checklist_solar_activity/3-Plotting-Helpers-plots.py.md +123 -0
- plans/completed/combined_test_plan_with_checklist_solar_activity/4-LISIRD-Sub-package.md +119 -0
- plans/completed/combined_test_plan_with_checklist_solar_activity/5-Extrema-Calculator.md +103 -0
- plans/completed/combined_test_plan_with_checklist_solar_activity/6-Sunspot-Number-Sub-package.md +163 -0
- plans/completed/combined_test_plan_with_checklist_solar_activity/7-Sunspot-Number-Init.py.md +217 -0
- plans/completed/combined_test_plan_with_checklist_solar_activity/compacted_state.md +52 -0
- plans/completed/compaction-agent-modernization/0-Overview.md +156 -0
- plans/completed/compaction-agent-modernization/1-Architecture-Audit-Gap-Analysis.md +132 -0
- plans/completed/compaction-agent-modernization/2-Token-Baseline-Recalibration.md +153 -0
- plans/completed/compaction-agent-modernization/3-Agent-Reference-Updates.md +184 -0
- plans/completed/compaction-agent-modernization/4-Compression-Algorithm-Modernization.md +238 -0
- plans/completed/compaction-agent-modernization/5-Workflow-Integration-Streamlining.md +252 -0
- plans/completed/compaction-agent-modernization/6-Template-Structure-Optimization.md +240 -0
- plans/completed/compaction-agent-modernization/7-Integration-Testing-Validation.md +292 -0
- plans/completed/compaction-hook-enhancement/0-Overview.md +150 -0
- plans/completed/compaction-hook-enhancement/1-Token-Estimation-Enhancement.md +179 -0
- plans/completed/compaction-hook-enhancement/2-Compression-Intelligence.md +294 -0
- plans/completed/compaction-hook-enhancement/3-Git-Integration-Metadata.md +310 -0
- plans/completed/compaction-hook-enhancement/4-Session-Continuity-Features.md +358 -0
- plans/completed/compaction-hook-enhancement/5-Testing-Strategy.md +404 -0
- plans/completed/compaction-hook-enhancement/6-Integration-Roadmap.md +319 -0
- plans/completed/compaction-hook-enhancement/compacted_state.md +142 -0
- plans/completed/docstring-audit-enhancement/0-Overview.md +274 -0
- plans/completed/docstring-audit-enhancement/1-Infrastructure-Setup-and-Validation-Tools.md +206 -0
- plans/completed/docstring-audit-enhancement/2-Core-Physics-Modules-Enhancement.md +237 -0
- plans/completed/docstring-audit-enhancement/3-Fitfunctions-Mathematical-Modules-Enhancement.md +188 -0
- plans/completed/docstring-audit-enhancement/4-Plotting-Visualization-Modules-Enhancement.md +243 -0
- plans/completed/docstring-audit-enhancement/5-Specialized-Modules-Enhancement.md +216 -0
- plans/completed/docstring-audit-enhancement/6-Validation-and-Integration.md +216 -0
- plans/completed/fitfunctions-testing-implementation/0-Overview.md +130 -0
- plans/completed/fitfunctions-testing-implementation/1-Test-Infrastructure-Setup.md +79 -0
- plans/completed/fitfunctions-testing-implementation/2-Common-Fixtures-Test-Utilities.md +104 -0
- plans/completed/fitfunctions-testing-implementation/3-Core-FitFunction-Testing.md +168 -0
- plans/completed/fitfunctions-testing-implementation/4-Specialized-Function-Classes.md +210 -0
- plans/completed/fitfunctions-testing-implementation/5-Advanced-Classes-Testing.md +214 -0
- plans/completed/fitfunctions-testing-implementation/6-Plotting-Integration-Testing.md +231 -0
- plans/completed/fitfunctions-testing-implementation/7-Extended-Coverage-BONUS.md +184 -0
- plans/completed/numpy-docstring-conversion-plan/numpy-docstring-conversion-plan.md +118 -0
- plans/completed/pr-review-remediation/0-Overview.md +138 -0
- plans/completed/pr-review-remediation/1-Critical-Safety-Improvements.md +179 -0
- plans/completed/pr-review-remediation/2-Smart-Timeouts-Validation.md +399 -0
- plans/completed/pr-review-remediation/3-Enhanced-GitHub-Integration.md +258 -0
- plans/completed/pr-review-remediation/compacted_state.md +66 -0
- plans/completed/python-310-migration/0-Overview.md +390 -0
- plans/completed/python-310-migration/1-Planning-Setup.md +164 -0
- plans/completed/python-310-migration/2-Implementation.md +256 -0
- plans/completed/python-310-migration/3-Testing-Validation.md +335 -0
- plans/completed/python-310-migration/4-Documentation-Release.md +274 -0
- plans/completed/python-310-migration/5-Closeout.md +252 -0
- plans/completed/requirements-management-consolidation/0-Overview.md +118 -0
- plans/completed/requirements-management-consolidation/1-Documentation-Validation-Environment-Setup.md +116 -0
- plans/completed/requirements-management-consolidation/2-Requirements-Consolidation.md +161 -0
- plans/completed/requirements-management-consolidation/3-Workflow-Automation-Final-Integration.md +196 -0
- plans/completed/single-ecosystem-plan-implementation/0-Overview.md +83 -0
- plans/completed/single-ecosystem-plan-implementation/1-Plan-Preservation-Session-Management.md +38 -0
- plans/completed/single-ecosystem-plan-implementation/2-File-Structure-Optimization.md +43 -0
- plans/completed/single-ecosystem-plan-implementation/3-Plan-Migration-Archive-Setup.md +82 -0
- plans/completed/single-ecosystem-plan-implementation/4-Agent-System-Transformation.md +108 -0
- plans/completed/single-ecosystem-plan-implementation/5-Template-System-Enhancement.md +131 -0
- plans/completed/single-ecosystem-plan-implementation/6-Final-Validation-Testing.md +120 -0
- plans/completed/test-directory-consolidation/0-Overview.md +51 -0
- plans/completed/test-directory-consolidation/1-Structure-Preparation.md +82 -0
- plans/completed/test-directory-consolidation/2-File-Migration.md +100 -0
- plans/completed/test-directory-consolidation/3-Import-Transformation.md +117 -0
- plans/completed/test-directory-consolidation/4-Configuration-Consolidation.md +140 -0
- plans/completed/test-directory-consolidation/5-Validation.md +152 -0
- plans/completed/test-directory-consolidation/6-Cleanup.md +156 -0
- plans/completed/test-planning-agents-architecture/0-Overview.md +79 -0
- plans/completed/test-planning-agents-architecture/1-Branch-Isolation-Testing.md +49 -0
- plans/completed/test-planning-agents-architecture/2-Cross-Branch-Coordination.md +51 -0
- plans/completed/test-planning-agents-architecture/3-Merge-Workflow-Testing.md +48 -0
- plans/deployment-semver-pypi-rtd/0-Overview.md +463 -0
- plans/deployment-semver-pypi-rtd/1-Semantic-Versioning-Foundation.md +136 -0
- plans/deployment-semver-pypi-rtd/2-PyPI-Deployment-Infrastructure.md +168 -0
- plans/deployment-semver-pypi-rtd/3-Release-Automation.md +214 -0
- plans/deployment-semver-pypi-rtd/4-Plan-Closeout.md +543 -0
- plans/deployment-semver-pypi-rtd/compacted_session_state.md +172 -0
- plans/deployment-semver-pypi-rtd/compacted_state.md +131 -0
- plans/documentation-code-audit/0-Overview.md +393 -0
- plans/documentation-code-audit/1-Discovery-Inventory.md +183 -0
- plans/documentation-code-audit/2-Execution-Environment-Setup.md +263 -0
- plans/documentation-code-audit/3-Systematic-Validation.md +322 -0
- plans/documentation-code-audit/4-Code-Example-Remediation.md +358 -0
- plans/documentation-code-audit/5-Physics-MultiIndex-Compliance.md +464 -0
- plans/documentation-code-audit/6-Doctest-Integration.md +523 -0
- plans/documentation-code-audit/7-Reporting-Documentation.md +498 -0
- plans/documentation-code-audit/8-Closeout.md +456 -0
- plans/documentation-rebuild-session/compacted_state.md +109 -0
- plans/documentation-rendering-fixes/0-Overview.md +104 -0
- plans/documentation-rendering-fixes/1-Sphinx-Build-Diagnostics-Warning-Audit.md +101 -0
- plans/documentation-rendering-fixes/2-Configuration-Infrastructure-Fixes.md +113 -0
- plans/documentation-rendering-fixes/3-Docstring-Syntax-Audit-Repair.md +131 -0
- plans/documentation-rendering-fixes/4-HTML-Page-Rendering-Verification.md +113 -0
- plans/documentation-rendering-fixes/5-Advanced-Documentation-Quality-Assurance.md +119 -0
- plans/documentation-rendering-fixes/6-Documentation-Build-Optimization-Testing.md +129 -0
- plans/documentation-rendering-fixes/compacted_state.md +132 -0
- plans/documentation-template-fix/0-Overview.md +197 -0
- plans/documentation-template-fix/1-Template-System-Analysis.md +269 -0
- plans/documentation-template-fix/2-Template-Modification.md +609 -0
- plans/documentation-template-fix/3-Build-System-Integration.md +766 -0
- plans/documentation-template-fix/4-Testing-Validation.md +1399 -0
- plans/documentation-template-fix/5-Documentation-Training.md +602 -0
- plans/documentation-workflow-fix/0-Overview.md +222 -0
- plans/documentation-workflow-fix/1-Immediate-Fixes.md +238 -0
- plans/documentation-workflow-fix/2-Configuration-Setup.md +298 -0
- plans/documentation-workflow-fix/3-Pre-commit-Integration.md +382 -0
- plans/documentation-workflow-fix/4-Workflow-Improvements.md +446 -0
- plans/documentation-workflow-fix/5-Documentation-and-Training.md +527 -0
- plans/duplicate-object-warnings-fix-plan.md +130 -0
- plans/github-issues-migration/0-Overview.md +510 -0
- plans/github-issues-migration/1-Foundation-Label-System.md +180 -0
- plans/github-issues-migration/2-Migration-Tool-Rewrite.md +235 -0
- plans/github-issues-migration/3-CLI-Integration-Automation.md +169 -0
- plans/github-issues-migration/4-Validated-Migration.md +252 -0
- plans/github-issues-migration/5-Documentation-Training.md +171 -0
- plans/github-issues-migration/6-Closeout.md +179 -0
- plans/github-workflows-repair/repair-plan.md +299 -0
- plans/issues_from_plans.py +342 -0
- plans/pr-270-doc-validation-fixes/0-Overview.md +354 -0
- plans/pr-270-doc-validation-fixes/1-Critical-PR-Fixes.md +117 -0
- plans/pr-270-doc-validation-fixes/2-Framework-Right-Sizing.md +129 -0
- plans/pr-270-doc-validation-fixes/3-Sustainable-Documentation.md +126 -0
- plans/pr-270-doc-validation-fixes/4-Closeout-Migration.md +143 -0
- plans/pr-270-doc-validation-fixes/PLAN_COMPLETED.md +149 -0
- plans/python-310-migration/0-Overview.md +390 -0
- plans/python-310-migration/1-Planning-Setup.md +164 -0
- plans/python-310-migration/2-Implementation.md +256 -0
- plans/python-310-migration/3-Testing-Validation.md +335 -0
- plans/python-310-migration/4-Documentation-Release.md +274 -0
- plans/python-310-migration/5-Closeout.md +252 -0
- plans/readthedocs-simplified/0-Overview.md +243 -0
- plans/readthedocs-simplified/1-Immediate-Fixes.md +216 -0
- plans/readthedocs-simplified/2-Template-Simplification.md +278 -0
- plans/readthedocs-simplified/3-ReadTheDocs-Setup.md +298 -0
- plans/readthedocs-simplified/4-Testing-Validation.md +328 -0
- plans/readthedocs-simplified/5-Closeout.md +231 -0
- plans/readthedocs-simplified/compacted_state.md +127 -0
- plans/session-compaction-2025-08-12/compacted_state.md +114 -0
- plans/session-compaction-2025-08-13/compacted_state.md +145 -0
- plans/session-continuity-protocol/0-Overview.md +35 -0
- plans/session-continuity-protocol/1-Core-Principles-Framework.md +40 -0
- plans/session-continuity-protocol/2-Pre-Session-Validation-System.md +79 -0
- plans/session-continuity-protocol/3-Context-Switching-Prevention.md +87 -0
- plans/session-continuity-protocol/4-Progress-Tracking-Recovery.md +100 -0
- plans/sphinx-warnings-analysis.md +222 -0
- plans/systemprompt-optimization/0-Overview.md +447 -0
- plans/systemprompt-optimization/1-Deploy-SystemPrompt.md +114 -0
- plans/systemprompt-optimization/2-Documentation-Alignment.md +198 -0
- plans/systemprompt-optimization/3-Monitoring-Infrastructure.md +396 -0
- plans/systemprompt-optimization/4-Implementation-Script.md +450 -0
- plans/systemprompt-optimization/9-Closeout.md +165 -0
- plans/systemprompt-optimization/compacted_state.md +143 -0
- plans/template-value-propositions/0-Overview.md +357 -0
- plans/template-value-propositions/1-Value-Proposition-Framework-Design.md +144 -0
- plans/template-value-propositions/2-Plan-Template-Enhancement.md +178 -0
- plans/template-value-propositions/3-Value-Generator-Hook-Implementation.md +291 -0
- plans/template-value-propositions/4-Value-Validator-Hook-Implementation.md +274 -0
- plans/template-value-propositions/5-Documentation-Agent-Updates.md +219 -0
- plans/template-value-propositions/6-Integration-Testing-Validation.md +247 -0
- plans/tests-audit/0-Overview.md +410 -0
- plans/tests-audit/1-Discovery-Inventory.md +170 -0
- plans/tests-audit/2-Physics-Validation-Audit.md +195 -0
- plans/tests-audit/3-Architecture-Compliance.md +195 -0
- plans/tests-audit/4-Numerical-Stability-Analysis.md +203 -0
- plans/tests-audit/5-Documentation-Enhancement.md +220 -0
- plans/tests-audit/6-Audit-Deliverables.md +220 -0
- plans/tests-audit/7-Closeout.md +252 -0
- plans/tests-audit/artifacts/ARCHITECTURE_COMPLIANCE_REPORT.md +315 -0
- plans/tests-audit/artifacts/ARCHITECTURE_RECOMMENDATIONS.md +943 -0
- plans/tests-audit/artifacts/COMPREHENSIVE_AUDIT_REPORT.md +356 -0
- plans/tests-audit/artifacts/CONTRIBUTING_ENHANCED_TEMPLATE.md +419 -0
- plans/tests-audit/artifacts/COVERAGE_GAP_ANALYSIS.md +152 -0
- plans/tests-audit/artifacts/DOCUMENTATION_ENHANCEMENT_REPORT.md +502 -0
- plans/tests-audit/artifacts/EXECUTIVE_AUDIT_SUMMARY.md +129 -0
- plans/tests-audit/artifacts/IMPLEMENTATION_ROADMAP.md +647 -0
- plans/tests-audit/artifacts/NUMERICAL_RECOMMENDATIONS.md +739 -0
- plans/tests-audit/artifacts/NUMERICAL_STABILITY_GUIDE_TEMPLATE.rst +451 -0
- plans/tests-audit/artifacts/NUMERICAL_STABILITY_REPORT.md +301 -0
- plans/tests-audit/artifacts/PHASE_3_SUMMARY.md +280 -0
- plans/tests-audit/artifacts/PHASE_4_SUMMARY.md +229 -0
- plans/tests-audit/artifacts/PHASE_5_SUMMARY.md +292 -0
- plans/tests-audit/artifacts/PHASE_6_CLOSEOUT.md +278 -0
- plans/tests-audit/artifacts/PHYSICS_GUIDE_TEMPLATE.rst +268 -0
- plans/tests-audit/artifacts/PHYSICS_VALIDATION_REPORT.md +235 -0
- plans/tests-audit/artifacts/TECHNICAL_DELIVERABLES_PACKAGE.md +2502 -0
- plans/tests-audit/artifacts/TEST_INVENTORY.csv +1204 -0
- plans/tests-audit/artifacts/TEST_INVENTORY.md +135 -0
- plans/tests-audit/artifacts/test_discovery_analysis.py +231 -0
- plans/tests-audit/artifacts/test_parser.py +395 -0
- solarwindpy/README.md +3 -0
- solarwindpy/Untitled.ipynb +54 -0
- solarwindpy/__init__.py +74 -0
- solarwindpy/core/__init__.py +23 -0
- solarwindpy/core/alfvenic_turbulence.py +804 -0
- solarwindpy/core/base.py +267 -0
- solarwindpy/core/ions.py +309 -0
- solarwindpy/core/plasma.py +2133 -0
- solarwindpy/core/spacecraft.py +256 -0
- solarwindpy/core/tensor.py +90 -0
- solarwindpy/core/units_constants.py +199 -0
- solarwindpy/core/vector.py +328 -0
- solarwindpy/fitfunctions/__init__.py +20 -0
- solarwindpy/fitfunctions/core.py +734 -0
- solarwindpy/fitfunctions/exponentials.py +188 -0
- solarwindpy/fitfunctions/gaussians.py +264 -0
- solarwindpy/fitfunctions/lines.py +116 -0
- solarwindpy/fitfunctions/moyal.py +71 -0
- solarwindpy/fitfunctions/plots.py +751 -0
- solarwindpy/fitfunctions/power_laws.py +209 -0
- solarwindpy/fitfunctions/tex_info.py +568 -0
- solarwindpy/fitfunctions/trend_fits.py +482 -0
- solarwindpy/instabilities/__init__.py +16 -0
- solarwindpy/instabilities/beta_ani.py +82 -0
- solarwindpy/instabilities/verscharen2016.py +631 -0
- solarwindpy/plotting/__init__.py +33 -0
- solarwindpy/plotting/agg_plot.py +489 -0
- solarwindpy/plotting/base.py +465 -0
- solarwindpy/plotting/hist1d.py +405 -0
- solarwindpy/plotting/hist2d.py +1035 -0
- solarwindpy/plotting/histograms.py +1845 -0
- solarwindpy/plotting/labels/__init__.py +104 -0
- solarwindpy/plotting/labels/base.py +686 -0
- solarwindpy/plotting/labels/chemistry.py +19 -0
- solarwindpy/plotting/labels/composition.py +100 -0
- solarwindpy/plotting/labels/datetime.py +235 -0
- solarwindpy/plotting/labels/elemental_abundance.py +73 -0
- solarwindpy/plotting/labels/special.py +794 -0
- solarwindpy/plotting/orbits.py +515 -0
- solarwindpy/plotting/scatter.py +99 -0
- solarwindpy/plotting/select_data_from_figure.py +329 -0
- solarwindpy/plotting/spiral.py +980 -0
- solarwindpy/plotting/tools.py +434 -0
- solarwindpy/scripts/__init__.py +1 -0
- solarwindpy/scripts/logs/.gitignore +1 -0
- solarwindpy/solar_activity/__init__.py +53 -0
- solarwindpy/solar_activity/base.py +605 -0
- solarwindpy/solar_activity/lisird/__init__.py +3 -0
- solarwindpy/solar_activity/lisird/extrema_calculator.py +394 -0
- solarwindpy/solar_activity/lisird/lisird.py +319 -0
- solarwindpy/solar_activity/plots.py +116 -0
- solarwindpy/solar_activity/sunspot_number/.DS_Store +0 -0
- solarwindpy/solar_activity/sunspot_number/__init__.py +3 -0
- solarwindpy/solar_activity/sunspot_number/sidc.py +556 -0
- solarwindpy/solar_activity/sunspot_number/ssn_extrema.csv +72 -0
- solarwindpy/solar_activity/sunspot_number/ssn_extrema.csv.silso +72 -0
- solarwindpy/tools/__init__.py +162 -0
- solarwindpy-0.1.1.dist-info/METADATA +181 -0
- solarwindpy-0.1.1.dist-info/RECORD +409 -0
- {solarwindpy-0.0.1.dev0.dist-info → solarwindpy-0.1.1.dist-info}/WHEEL +1 -1
- solarwindpy-0.1.1.dist-info/licenses/LICENSE.rst +32 -0
- solarwindpy-0.1.1.dist-info/top_level.txt +3 -0
- tests/__init__.py +1 -0
- tests/conftest.py +10 -0
- tests/core/__init__.py +1 -0
- tests/core/test_alfvenic_turbulence.py +544 -0
- tests/core/test_base.py +112 -0
- tests/core/test_base_head_tail.py +29 -0
- tests/core/test_base_mi_tuples.py +11 -0
- tests/core/test_core_verify_datetimeindex.py +32 -0
- tests/core/test_ions.py +325 -0
- tests/core/test_plasma.py +2581 -0
- tests/core/test_plasma_io.py +12 -0
- tests/core/test_quantities.py +507 -0
- tests/core/test_spacecraft.py +210 -0
- tests/core/test_units_constants.py +22 -0
- tests/data/epoch.csv +4 -0
- tests/data/plasma.csv +4 -0
- tests/data/spacecraft.csv +4 -0
- tests/fitfunctions/conftest.py +60 -0
- tests/fitfunctions/test_core.py +193 -0
- tests/fitfunctions/test_exponentials.py +342 -0
- tests/fitfunctions/test_gaussians.py +142 -0
- tests/fitfunctions/test_lines.py +349 -0
- tests/fitfunctions/test_moyal.py +258 -0
- tests/fitfunctions/test_plots.py +258 -0
- tests/fitfunctions/test_power_laws.py +365 -0
- tests/fitfunctions/test_tex_info.py +183 -0
- tests/fitfunctions/test_trend_fit_properties.py +31 -0
- tests/fitfunctions/test_trend_fits.py +244 -0
- tests/plotting/__init__.py +1 -0
- tests/plotting/labels/__init__.py +1 -0
- tests/plotting/labels/test_chemistry.py +243 -0
- tests/plotting/labels/test_composition.py +345 -0
- tests/plotting/labels/test_datetime.py +445 -0
- tests/plotting/labels/test_elemental_abundance.py +366 -0
- tests/plotting/labels/test_init.py +66 -0
- tests/plotting/labels/test_labels_base.py +347 -0
- tests/plotting/labels/test_special.py +550 -0
- tests/plotting/test_agg_plot.py +602 -0
- tests/plotting/test_base.py +752 -0
- tests/plotting/test_fixtures_utilities.py +775 -0
- tests/plotting/test_histograms.py +546 -0
- tests/plotting/test_integration.py +675 -0
- tests/plotting/test_orbits.py +435 -0
- tests/plotting/test_performance.py +708 -0
- tests/plotting/test_scatter.py +752 -0
- tests/plotting/test_select_data_from_figure.py +1209 -0
- tests/plotting/test_spiral.py +573 -0
- tests/plotting/test_tools.py +607 -0
- tests/plotting/test_visual_validation.py +465 -0
- tests/solar_activity/__init__.py +1 -0
- tests/solar_activity/lisird/__init__.py +1 -0
- tests/solar_activity/lisird/test_extrema_calculator.py +593 -0
- tests/solar_activity/lisird/test_lisird_id.py +187 -0
- tests/solar_activity/sunspot_number/__init__.py +1 -0
- tests/solar_activity/sunspot_number/test_init.py +399 -0
- tests/solar_activity/sunspot_number/test_sidc.py +465 -0
- tests/solar_activity/sunspot_number/test_sidc_id.py +223 -0
- tests/solar_activity/sunspot_number/test_sidc_loader.py +275 -0
- tests/solar_activity/sunspot_number/test_ssn_extrema.py +406 -0
- tests/solar_activity/test_base.py +656 -0
- tests/solar_activity/test_init.py +396 -0
- tests/solar_activity/test_plots.py +371 -0
- tests/test_circular_imports.py +408 -0
- tests/test_issue_titles.py +25 -0
- tests/test_statusline.py +298 -0
- solarwindpy-0.0.1.dev0.dist-info/METADATA +0 -14
- solarwindpy-0.0.1.dev0.dist-info/RECORD +0 -4
- solarwindpy-0.0.1.dev0.dist-info/top_level.txt +0 -1
|
@@ -0,0 +1,1209 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""Tests for solarwindpy.plotting.select_data_from_figure module.
|
|
3
|
+
|
|
4
|
+
This module provides comprehensive test coverage for the SelectFromPlot2D class used for
|
|
5
|
+
interactive data selection from plotted figures.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
import logging
|
|
10
|
+
import pandas as pd
|
|
11
|
+
import numpy as np
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from unittest.mock import patch, MagicMock, call, Mock
|
|
14
|
+
from collections import namedtuple
|
|
15
|
+
|
|
16
|
+
import matplotlib
|
|
17
|
+
|
|
18
|
+
matplotlib.use("Agg") # Use non-interactive backend
|
|
19
|
+
from matplotlib import pyplot as plt
|
|
20
|
+
from matplotlib.widgets import RectangleSelector
|
|
21
|
+
from matplotlib.patches import Rectangle
|
|
22
|
+
from matplotlib.text import Text
|
|
23
|
+
|
|
24
|
+
import solarwindpy.plotting.select_data_from_figure as select_module
|
|
25
|
+
from solarwindpy.plotting.select_data_from_figure import SelectFromPlot2D, DateAxes
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class TestSelectDataFromFigureModuleStructure:
|
|
29
|
+
"""Test select_data_from_figure module structure and imports."""
|
|
30
|
+
|
|
31
|
+
def test_module_imports(self):
|
|
32
|
+
"""Test that all required imports are accessible."""
|
|
33
|
+
# Test basic imports
|
|
34
|
+
assert hasattr(select_module, "logging")
|
|
35
|
+
assert hasattr(select_module, "np")
|
|
36
|
+
assert hasattr(select_module, "pd")
|
|
37
|
+
assert hasattr(select_module, "mpl")
|
|
38
|
+
assert hasattr(select_module, "namedtuple")
|
|
39
|
+
|
|
40
|
+
def test_module_all_export(self):
|
|
41
|
+
"""Test that __all__ exports are correct."""
|
|
42
|
+
assert hasattr(select_module, "__all__")
|
|
43
|
+
assert "SelectFromPlot2D" in select_module.__all__
|
|
44
|
+
assert len(select_module.__all__) == 1
|
|
45
|
+
|
|
46
|
+
def test_date_axes_namedtuple(self):
|
|
47
|
+
"""Test DateAxes namedtuple structure."""
|
|
48
|
+
assert hasattr(select_module, "DateAxes")
|
|
49
|
+
assert DateAxes._fields == ("x", "y")
|
|
50
|
+
|
|
51
|
+
# Test creation
|
|
52
|
+
date_axes = DateAxes(True, False)
|
|
53
|
+
assert date_axes.x is True
|
|
54
|
+
assert date_axes.y is False
|
|
55
|
+
|
|
56
|
+
def test_select_from_plot_2d_class_available(self):
|
|
57
|
+
"""Test that SelectFromPlot2D class is accessible from module."""
|
|
58
|
+
assert hasattr(select_module, "SelectFromPlot2D")
|
|
59
|
+
assert callable(select_module.SelectFromPlot2D)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class TestSelectFromPlot2DInitialization:
|
|
63
|
+
"""Test SelectFromPlot2D class initialization and basic properties."""
|
|
64
|
+
|
|
65
|
+
@pytest.fixture
|
|
66
|
+
def mock_plotter(self):
|
|
67
|
+
"""Create a mock plotter object."""
|
|
68
|
+
plotter = Mock()
|
|
69
|
+
plotter.data = pd.DataFrame(
|
|
70
|
+
{
|
|
71
|
+
"x": np.random.randn(100),
|
|
72
|
+
"y": np.random.randn(100),
|
|
73
|
+
"z": np.random.randn(100),
|
|
74
|
+
}
|
|
75
|
+
)
|
|
76
|
+
plotter.log = Mock()
|
|
77
|
+
plotter.log.x = False
|
|
78
|
+
plotter.log.y = False
|
|
79
|
+
return plotter
|
|
80
|
+
|
|
81
|
+
@pytest.fixture
|
|
82
|
+
def mock_ax(self):
|
|
83
|
+
"""Create a mock axes object."""
|
|
84
|
+
ax = Mock()
|
|
85
|
+
ax.figure = Mock()
|
|
86
|
+
ax.figure.axes = [ax] # Single panel by default
|
|
87
|
+
ax.patches = []
|
|
88
|
+
ax.transAxes = Mock()
|
|
89
|
+
ax.text = Mock(return_value=Mock())
|
|
90
|
+
ax.xaxis = Mock()
|
|
91
|
+
ax.xaxis.get_label = Mock()
|
|
92
|
+
ax.xaxis.get_label.return_value = Mock()
|
|
93
|
+
ax.xaxis.get_label.return_value.get_text = Mock(return_value="x-axis")
|
|
94
|
+
ax.yaxis = Mock()
|
|
95
|
+
ax.yaxis.get_label = Mock()
|
|
96
|
+
ax.yaxis.get_label.return_value = Mock()
|
|
97
|
+
ax.yaxis.get_label.return_value.get_text = Mock(return_value="y-axis")
|
|
98
|
+
ax.figure.canvas = Mock()
|
|
99
|
+
ax.figure.canvas.draw_idle = Mock()
|
|
100
|
+
ax.add_patch = Mock()
|
|
101
|
+
ax.scatter = Mock()
|
|
102
|
+
ax.get_xlim = Mock(return_value=(0, 1))
|
|
103
|
+
ax.get_ylim = Mock(return_value=(0, 1))
|
|
104
|
+
ax.set_xlim = Mock()
|
|
105
|
+
ax.set_ylim = Mock()
|
|
106
|
+
return ax
|
|
107
|
+
|
|
108
|
+
@patch("solarwindpy.plotting.select_data_from_figure.mpl.widgets.RectangleSelector")
|
|
109
|
+
def test_init_basic(self, mock_rect_selector, mock_plotter, mock_ax):
|
|
110
|
+
"""Test basic initialization."""
|
|
111
|
+
mock_selector = Mock()
|
|
112
|
+
mock_rect_selector.return_value = mock_selector
|
|
113
|
+
|
|
114
|
+
selector = SelectFromPlot2D(mock_plotter, mock_ax)
|
|
115
|
+
|
|
116
|
+
# Test basic attributes
|
|
117
|
+
assert selector._plotter is mock_plotter
|
|
118
|
+
assert selector._ax is mock_ax
|
|
119
|
+
assert selector._corners == tuple()
|
|
120
|
+
assert hasattr(selector, "_date_axes")
|
|
121
|
+
assert hasattr(selector, "_text")
|
|
122
|
+
assert hasattr(selector, "_selector")
|
|
123
|
+
|
|
124
|
+
# Test initialization calls
|
|
125
|
+
mock_rect_selector.assert_called_once()
|
|
126
|
+
mock_ax.text.assert_called_once()
|
|
127
|
+
|
|
128
|
+
@patch("solarwindpy.plotting.select_data_from_figure.mpl.widgets.RectangleSelector")
|
|
129
|
+
def test_init_with_colorbar(self, mock_rect_selector, mock_plotter, mock_ax):
|
|
130
|
+
"""Test initialization with colorbar consideration."""
|
|
131
|
+
# Mock multi-panel with colorbar
|
|
132
|
+
mock_ax.figure.axes = [mock_ax, Mock(), Mock()] # 3 axes (2 + colorbar)
|
|
133
|
+
mock_selector = Mock()
|
|
134
|
+
mock_rect_selector.return_value = mock_selector
|
|
135
|
+
|
|
136
|
+
selector = SelectFromPlot2D(mock_plotter, mock_ax, has_colorbar=True)
|
|
137
|
+
|
|
138
|
+
# With colorbar=True and 3 axes: (3 - 1) = 2 > 1, so is_multipanel=True
|
|
139
|
+
assert selector.is_multipanel is True
|
|
140
|
+
|
|
141
|
+
@patch("solarwindpy.plotting.select_data_from_figure.mpl.widgets.RectangleSelector")
|
|
142
|
+
def test_init_date_axes(self, mock_rect_selector, mock_plotter, mock_ax):
|
|
143
|
+
"""Test initialization with date axes."""
|
|
144
|
+
mock_selector = Mock()
|
|
145
|
+
mock_rect_selector.return_value = mock_selector
|
|
146
|
+
|
|
147
|
+
selector = SelectFromPlot2D(mock_plotter, mock_ax, xdate=True, ydate=False)
|
|
148
|
+
|
|
149
|
+
assert selector.date_axes.x is True
|
|
150
|
+
assert selector.date_axes.y is False
|
|
151
|
+
|
|
152
|
+
@patch("solarwindpy.plotting.select_data_from_figure.mpl.widgets.RectangleSelector")
|
|
153
|
+
def test_init_text_kwargs(self, mock_rect_selector, mock_plotter, mock_ax):
|
|
154
|
+
"""Test initialization with custom text kwargs."""
|
|
155
|
+
mock_selector = Mock()
|
|
156
|
+
mock_rect_selector.return_value = mock_selector
|
|
157
|
+
text_kwargs = {"fontsize": 12, "color": "red"}
|
|
158
|
+
|
|
159
|
+
selector = SelectFromPlot2D(mock_plotter, mock_ax, text_kwargs=text_kwargs)
|
|
160
|
+
|
|
161
|
+
# Should initialize without error
|
|
162
|
+
assert hasattr(selector, "_text")
|
|
163
|
+
|
|
164
|
+
@patch("solarwindpy.plotting.select_data_from_figure.mpl.widgets.RectangleSelector")
|
|
165
|
+
def test_invalid_plotter_type(self, mock_rect_selector, mock_ax):
|
|
166
|
+
"""Test that invalid plotter type raises AttributeError when accessing plotter
|
|
167
|
+
attributes."""
|
|
168
|
+
mock_selector = Mock()
|
|
169
|
+
mock_rect_selector.return_value = mock_selector
|
|
170
|
+
|
|
171
|
+
# Initialization should work (plotter attributes not accessed yet)
|
|
172
|
+
selector = SelectFromPlot2D("invalid_plotter", mock_ax)
|
|
173
|
+
|
|
174
|
+
# But accessing plotter.data should fail
|
|
175
|
+
with pytest.raises(AttributeError):
|
|
176
|
+
selector._add_corners((0, 1, 0, 1))
|
|
177
|
+
selector.sample_data(n=3)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class TestSelectFromPlot2DProperties:
|
|
181
|
+
"""Test SelectFromPlot2D properties."""
|
|
182
|
+
|
|
183
|
+
@pytest.fixture
|
|
184
|
+
def selector_setup(self):
|
|
185
|
+
"""Set up a SelectFromPlot2D instance for testing."""
|
|
186
|
+
with patch(
|
|
187
|
+
"solarwindpy.plotting.select_data_from_figure.mpl.widgets.RectangleSelector"
|
|
188
|
+
):
|
|
189
|
+
plotter = Mock()
|
|
190
|
+
plotter.data = pd.DataFrame(
|
|
191
|
+
{
|
|
192
|
+
"x": np.random.randn(100),
|
|
193
|
+
"y": np.random.randn(100),
|
|
194
|
+
"z": np.random.randn(100),
|
|
195
|
+
}
|
|
196
|
+
)
|
|
197
|
+
plotter.log = Mock()
|
|
198
|
+
plotter.log.x = False
|
|
199
|
+
plotter.log.y = False
|
|
200
|
+
|
|
201
|
+
ax = Mock()
|
|
202
|
+
ax.figure = Mock()
|
|
203
|
+
ax.figure.axes = [ax]
|
|
204
|
+
ax.patches = []
|
|
205
|
+
ax.transAxes = Mock()
|
|
206
|
+
ax.text = Mock(return_value=Mock())
|
|
207
|
+
ax.xaxis = Mock()
|
|
208
|
+
ax.xaxis.get_label = Mock()
|
|
209
|
+
ax.xaxis.get_label.return_value = Mock()
|
|
210
|
+
ax.xaxis.get_label.return_value.get_text = Mock(return_value="x-axis")
|
|
211
|
+
ax.yaxis = Mock()
|
|
212
|
+
ax.yaxis.get_label = Mock()
|
|
213
|
+
ax.yaxis.get_label.return_value = Mock()
|
|
214
|
+
ax.yaxis.get_label.return_value.get_text = Mock(return_value="y-axis")
|
|
215
|
+
ax.figure.canvas = Mock()
|
|
216
|
+
ax.figure.canvas.draw_idle = Mock()
|
|
217
|
+
|
|
218
|
+
selector = SelectFromPlot2D(plotter, ax)
|
|
219
|
+
return selector, plotter, ax
|
|
220
|
+
|
|
221
|
+
def test_ax_property(self, selector_setup):
|
|
222
|
+
"""Test ax property."""
|
|
223
|
+
selector, plotter, ax = selector_setup
|
|
224
|
+
assert selector.ax is ax
|
|
225
|
+
|
|
226
|
+
def test_corners_property(self, selector_setup):
|
|
227
|
+
"""Test corners property."""
|
|
228
|
+
selector, plotter, ax = selector_setup
|
|
229
|
+
assert selector.corners == tuple()
|
|
230
|
+
|
|
231
|
+
# Test after adding corners
|
|
232
|
+
selector._add_corners((0, 1, 0, 1))
|
|
233
|
+
assert len(selector.corners) == 1
|
|
234
|
+
assert selector.corners[0] == (0, 1, 0, 1)
|
|
235
|
+
|
|
236
|
+
def test_date_axes_property(self, selector_setup):
|
|
237
|
+
"""Test date_axes property."""
|
|
238
|
+
selector, plotter, ax = selector_setup
|
|
239
|
+
assert hasattr(selector.date_axes, "x")
|
|
240
|
+
assert hasattr(selector.date_axes, "y")
|
|
241
|
+
|
|
242
|
+
def test_is_multipanel_property(self, selector_setup):
|
|
243
|
+
"""Test is_multipanel property."""
|
|
244
|
+
selector, plotter, ax = selector_setup
|
|
245
|
+
assert selector.is_multipanel is False # Single panel by default
|
|
246
|
+
|
|
247
|
+
def test_plotter_property(self, selector_setup):
|
|
248
|
+
"""Test plotter property."""
|
|
249
|
+
selector, plotter, ax = selector_setup
|
|
250
|
+
assert selector.plotter is plotter
|
|
251
|
+
|
|
252
|
+
def test_selector_property(self, selector_setup):
|
|
253
|
+
"""Test selector property."""
|
|
254
|
+
selector, plotter, ax = selector_setup
|
|
255
|
+
assert hasattr(selector, "selector")
|
|
256
|
+
assert selector.selector is not None
|
|
257
|
+
|
|
258
|
+
def test_text_property(self, selector_setup):
|
|
259
|
+
"""Test text property."""
|
|
260
|
+
selector, plotter, ax = selector_setup
|
|
261
|
+
assert hasattr(selector, "text")
|
|
262
|
+
assert selector.text is not None
|
|
263
|
+
|
|
264
|
+
def test_num_initial_patches_property(self, selector_setup):
|
|
265
|
+
"""Test num_initial_patches property."""
|
|
266
|
+
selector, plotter, ax = selector_setup
|
|
267
|
+
assert hasattr(selector, "num_initial_patches")
|
|
268
|
+
assert isinstance(selector.num_initial_patches, int)
|
|
269
|
+
|
|
270
|
+
def test_num_selection_patches_property(self, selector_setup):
|
|
271
|
+
"""Test num_selection_patches property."""
|
|
272
|
+
selector, plotter, ax = selector_setup
|
|
273
|
+
# Initially should be 0
|
|
274
|
+
assert selector.num_selection_patches == 0
|
|
275
|
+
|
|
276
|
+
def test_logger_property(self, selector_setup):
|
|
277
|
+
"""Test logger property."""
|
|
278
|
+
selector, plotter, ax = selector_setup
|
|
279
|
+
logger = selector.logger
|
|
280
|
+
assert isinstance(logger, logging.Logger)
|
|
281
|
+
assert "analysis" in logger.name
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
class TestSelectFromPlot2DCornerManagement:
|
|
285
|
+
"""Test corner management methods."""
|
|
286
|
+
|
|
287
|
+
@pytest.fixture
|
|
288
|
+
def selector_setup(self):
|
|
289
|
+
"""Set up a SelectFromPlot2D instance for testing."""
|
|
290
|
+
with patch(
|
|
291
|
+
"solarwindpy.plotting.select_data_from_figure.mpl.widgets.RectangleSelector"
|
|
292
|
+
):
|
|
293
|
+
plotter = Mock()
|
|
294
|
+
plotter.data = pd.DataFrame(
|
|
295
|
+
{
|
|
296
|
+
"x": np.random.randn(100),
|
|
297
|
+
"y": np.random.randn(100),
|
|
298
|
+
"z": np.random.randn(100),
|
|
299
|
+
}
|
|
300
|
+
)
|
|
301
|
+
plotter.log = Mock()
|
|
302
|
+
plotter.log.x = False
|
|
303
|
+
plotter.log.y = False
|
|
304
|
+
|
|
305
|
+
ax = Mock()
|
|
306
|
+
ax.figure = Mock()
|
|
307
|
+
ax.figure.axes = [ax]
|
|
308
|
+
ax.patches = []
|
|
309
|
+
ax.transAxes = Mock()
|
|
310
|
+
ax.text = Mock(return_value=Mock())
|
|
311
|
+
ax.xaxis = Mock()
|
|
312
|
+
ax.xaxis.get_label = Mock()
|
|
313
|
+
ax.xaxis.get_label.return_value = Mock()
|
|
314
|
+
ax.xaxis.get_label.return_value.get_text = Mock(return_value="x-axis")
|
|
315
|
+
ax.yaxis = Mock()
|
|
316
|
+
ax.yaxis.get_label = Mock()
|
|
317
|
+
ax.yaxis.get_label.return_value = Mock()
|
|
318
|
+
ax.yaxis.get_label.return_value.get_text = Mock(return_value="y-axis")
|
|
319
|
+
ax.figure.canvas = Mock()
|
|
320
|
+
ax.figure.canvas.draw_idle = Mock()
|
|
321
|
+
|
|
322
|
+
selector = SelectFromPlot2D(plotter, ax)
|
|
323
|
+
return selector, plotter, ax
|
|
324
|
+
|
|
325
|
+
def test_init_corners(self, selector_setup):
|
|
326
|
+
"""Test _init_corners method."""
|
|
327
|
+
selector, plotter, ax = selector_setup
|
|
328
|
+
|
|
329
|
+
# Corners should be initialized as empty tuple
|
|
330
|
+
assert selector.corners == tuple()
|
|
331
|
+
|
|
332
|
+
def test_add_corners(self, selector_setup):
|
|
333
|
+
"""Test _add_corners method."""
|
|
334
|
+
selector, plotter, ax = selector_setup
|
|
335
|
+
|
|
336
|
+
# Add first corner
|
|
337
|
+
corner1 = (0, 1, 0, 1)
|
|
338
|
+
selector._add_corners(corner1)
|
|
339
|
+
assert len(selector.corners) == 1
|
|
340
|
+
assert selector.corners[0] == corner1
|
|
341
|
+
|
|
342
|
+
# Add second corner
|
|
343
|
+
corner2 = (2, 3, 2, 3)
|
|
344
|
+
selector._add_corners(corner2)
|
|
345
|
+
assert len(selector.corners) == 2
|
|
346
|
+
assert selector.corners[0] == corner1
|
|
347
|
+
assert selector.corners[1] == corner2
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
class TestSelectFromPlot2DTextManagement:
|
|
351
|
+
"""Test text management methods."""
|
|
352
|
+
|
|
353
|
+
@pytest.fixture
|
|
354
|
+
def selector_setup(self):
|
|
355
|
+
"""Set up a SelectFromPlot2D instance for testing."""
|
|
356
|
+
with patch(
|
|
357
|
+
"solarwindpy.plotting.select_data_from_figure.mpl.widgets.RectangleSelector"
|
|
358
|
+
):
|
|
359
|
+
plotter = Mock()
|
|
360
|
+
plotter.data = pd.DataFrame(
|
|
361
|
+
{
|
|
362
|
+
"x": np.random.randn(100),
|
|
363
|
+
"y": np.random.randn(100),
|
|
364
|
+
"z": np.random.randn(100),
|
|
365
|
+
}
|
|
366
|
+
)
|
|
367
|
+
plotter.log = Mock()
|
|
368
|
+
plotter.log.x = False
|
|
369
|
+
plotter.log.y = False
|
|
370
|
+
|
|
371
|
+
ax = Mock()
|
|
372
|
+
ax.figure = Mock()
|
|
373
|
+
ax.figure.axes = [ax]
|
|
374
|
+
ax.patches = []
|
|
375
|
+
ax.transAxes = Mock()
|
|
376
|
+
ax.text = Mock(return_value=Mock())
|
|
377
|
+
ax.xaxis = Mock()
|
|
378
|
+
ax.xaxis.get_label = Mock()
|
|
379
|
+
ax.xaxis.get_label.return_value = Mock()
|
|
380
|
+
ax.xaxis.get_label.return_value.get_text = Mock(return_value="x-axis")
|
|
381
|
+
ax.yaxis = Mock()
|
|
382
|
+
ax.yaxis.get_label = Mock()
|
|
383
|
+
ax.yaxis.get_label.return_value = Mock()
|
|
384
|
+
ax.yaxis.get_label.return_value.get_text = Mock(return_value="y-axis")
|
|
385
|
+
ax.figure.canvas = Mock()
|
|
386
|
+
ax.figure.canvas.draw_idle = Mock()
|
|
387
|
+
|
|
388
|
+
selector = SelectFromPlot2D(plotter, ax)
|
|
389
|
+
# Mock required attributes for text methods
|
|
390
|
+
selector._sampled_indices = pd.Index([1, 2, 3])
|
|
391
|
+
selector._failed_samples = []
|
|
392
|
+
selector._sampled_per_patch = 3
|
|
393
|
+
|
|
394
|
+
return selector, plotter, ax
|
|
395
|
+
|
|
396
|
+
def test_finalize_text_single_panel(self, selector_setup):
|
|
397
|
+
"""Test _finalize_text method for single panel."""
|
|
398
|
+
selector, plotter, ax = selector_setup
|
|
399
|
+
selector._is_multipanel = False
|
|
400
|
+
|
|
401
|
+
# Mock the text object
|
|
402
|
+
mock_text = Mock()
|
|
403
|
+
selector._text = mock_text
|
|
404
|
+
|
|
405
|
+
selector._finalize_text()
|
|
406
|
+
|
|
407
|
+
# Should call set_text with formatted string
|
|
408
|
+
mock_text.set_text.assert_called_once()
|
|
409
|
+
call_args = mock_text.set_text.call_args[0][0]
|
|
410
|
+
assert "Patches" in call_args
|
|
411
|
+
assert "Spectra" in call_args
|
|
412
|
+
assert " - " in call_args # Single panel format
|
|
413
|
+
|
|
414
|
+
def test_finalize_text_multi_panel(self, selector_setup):
|
|
415
|
+
"""Test _finalize_text method for multi panel."""
|
|
416
|
+
selector, plotter, ax = selector_setup
|
|
417
|
+
selector._is_multipanel = True
|
|
418
|
+
|
|
419
|
+
# Mock the text object
|
|
420
|
+
mock_text = Mock()
|
|
421
|
+
selector._text = mock_text
|
|
422
|
+
|
|
423
|
+
selector._finalize_text()
|
|
424
|
+
|
|
425
|
+
# Should call set_text with formatted string
|
|
426
|
+
mock_text.set_text.assert_called_once()
|
|
427
|
+
call_args = mock_text.set_text.call_args[0][0]
|
|
428
|
+
assert "Patches" in call_args
|
|
429
|
+
assert "Spectra" in call_args
|
|
430
|
+
assert "\n" in call_args # Multi panel format
|
|
431
|
+
|
|
432
|
+
def test_update_text_no_dates(self, selector_setup):
|
|
433
|
+
"""Test _update_text method without date axes."""
|
|
434
|
+
selector, plotter, ax = selector_setup
|
|
435
|
+
|
|
436
|
+
# Mock selector with extents
|
|
437
|
+
mock_selector = Mock()
|
|
438
|
+
mock_selector.extents = (0.1, 0.9, 0.2, 0.8)
|
|
439
|
+
selector._selector = mock_selector
|
|
440
|
+
|
|
441
|
+
# Mock text object
|
|
442
|
+
mock_text = Mock()
|
|
443
|
+
selector._text = mock_text
|
|
444
|
+
|
|
445
|
+
selector._update_text()
|
|
446
|
+
|
|
447
|
+
mock_text.set_text.assert_called_once()
|
|
448
|
+
call_args = mock_text.set_text.call_args[0][0]
|
|
449
|
+
assert "Patch" in call_args
|
|
450
|
+
assert "Lower Left" in call_args
|
|
451
|
+
assert "Upper Right" in call_args
|
|
452
|
+
|
|
453
|
+
@patch("solarwindpy.plotting.select_data_from_figure.mpl.dates.num2date")
|
|
454
|
+
def test_update_text_with_dates(self, mock_num2date, selector_setup):
|
|
455
|
+
"""Test _update_text method with date axes."""
|
|
456
|
+
selector, plotter, ax = selector_setup
|
|
457
|
+
|
|
458
|
+
# Set date axes
|
|
459
|
+
selector._date_axes = DateAxes(True, True)
|
|
460
|
+
|
|
461
|
+
# Mock date conversion
|
|
462
|
+
mock_date = Mock()
|
|
463
|
+
mock_date.strftime = Mock(return_value="2020-01-01 12:00:00")
|
|
464
|
+
mock_num2date.return_value = [mock_date, mock_date]
|
|
465
|
+
|
|
466
|
+
# Mock selector with extents
|
|
467
|
+
mock_selector = Mock()
|
|
468
|
+
mock_selector.extents = (737425.5, 737426.5, 737425.5, 737426.5) # date numbers
|
|
469
|
+
selector._selector = mock_selector
|
|
470
|
+
|
|
471
|
+
# Mock text object
|
|
472
|
+
mock_text = Mock()
|
|
473
|
+
selector._text = mock_text
|
|
474
|
+
|
|
475
|
+
selector._update_text()
|
|
476
|
+
|
|
477
|
+
mock_text.set_text.assert_called_once()
|
|
478
|
+
mock_num2date.assert_called()
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
class TestSelectFromPlot2DSelection:
|
|
482
|
+
"""Test selection-related methods."""
|
|
483
|
+
|
|
484
|
+
@pytest.fixture
|
|
485
|
+
def selector_setup(self):
|
|
486
|
+
"""Set up a SelectFromPlot2D instance for testing."""
|
|
487
|
+
with patch(
|
|
488
|
+
"solarwindpy.plotting.select_data_from_figure.mpl.widgets.RectangleSelector"
|
|
489
|
+
):
|
|
490
|
+
plotter = Mock()
|
|
491
|
+
plotter.data = pd.DataFrame(
|
|
492
|
+
{
|
|
493
|
+
"x": np.random.randn(100),
|
|
494
|
+
"y": np.random.randn(100),
|
|
495
|
+
"z": np.random.randn(100),
|
|
496
|
+
}
|
|
497
|
+
)
|
|
498
|
+
plotter.log = Mock()
|
|
499
|
+
plotter.log.x = False
|
|
500
|
+
plotter.log.y = False
|
|
501
|
+
|
|
502
|
+
ax = Mock()
|
|
503
|
+
ax.figure = Mock()
|
|
504
|
+
ax.figure.axes = [ax]
|
|
505
|
+
ax.patches = []
|
|
506
|
+
ax.transAxes = Mock()
|
|
507
|
+
ax.text = Mock(return_value=Mock())
|
|
508
|
+
ax.xaxis = Mock()
|
|
509
|
+
ax.xaxis.get_label = Mock()
|
|
510
|
+
ax.xaxis.get_label.return_value = Mock()
|
|
511
|
+
ax.xaxis.get_label.return_value.get_text = Mock(return_value="x-axis")
|
|
512
|
+
ax.yaxis = Mock()
|
|
513
|
+
ax.yaxis.get_label = Mock()
|
|
514
|
+
ax.yaxis.get_label.return_value = Mock()
|
|
515
|
+
ax.yaxis.get_label.return_value.get_text = Mock(return_value="y-axis")
|
|
516
|
+
ax.figure.canvas = Mock()
|
|
517
|
+
ax.figure.canvas.draw_idle = Mock()
|
|
518
|
+
ax.add_patch = Mock()
|
|
519
|
+
ax.scatter = Mock()
|
|
520
|
+
ax.get_xlim = Mock(return_value=(0, 1))
|
|
521
|
+
ax.get_ylim = Mock(return_value=(0, 1))
|
|
522
|
+
ax.set_xlim = Mock()
|
|
523
|
+
ax.set_ylim = Mock()
|
|
524
|
+
|
|
525
|
+
selector = SelectFromPlot2D(plotter, ax)
|
|
526
|
+
return selector, plotter, ax
|
|
527
|
+
|
|
528
|
+
@patch("solarwindpy.plotting.select_data_from_figure.mpl.patches.Rectangle")
|
|
529
|
+
def test_onselect(self, mock_rectangle, selector_setup):
|
|
530
|
+
"""Test onselect method."""
|
|
531
|
+
selector, plotter, ax = selector_setup
|
|
532
|
+
|
|
533
|
+
# Mock selector rect_bbox
|
|
534
|
+
mock_selector = Mock()
|
|
535
|
+
mock_selector._rect_bbox = [0, 0, 1, 1] # x, y, w, h
|
|
536
|
+
mock_selector.extents = (0, 1, 0, 1)
|
|
537
|
+
selector._selector = mock_selector
|
|
538
|
+
|
|
539
|
+
# Mock text object
|
|
540
|
+
mock_text = Mock()
|
|
541
|
+
selector._text = mock_text
|
|
542
|
+
|
|
543
|
+
# Mock rectangle patch
|
|
544
|
+
mock_rect = Mock()
|
|
545
|
+
mock_rectangle.return_value = mock_rect
|
|
546
|
+
|
|
547
|
+
selector.onselect("press", "release")
|
|
548
|
+
|
|
549
|
+
# Should create rectangle and add to axis
|
|
550
|
+
mock_rectangle.assert_called_once()
|
|
551
|
+
ax.add_patch.assert_called_once_with(mock_rect)
|
|
552
|
+
|
|
553
|
+
# Should update corners and text
|
|
554
|
+
assert len(selector.corners) == 1
|
|
555
|
+
mock_text.set_text.assert_called()
|
|
556
|
+
ax.figure.canvas.draw_idle.assert_called()
|
|
557
|
+
|
|
558
|
+
def test_set_ax(self, selector_setup):
|
|
559
|
+
"""Test set_ax method."""
|
|
560
|
+
selector, plotter, ax = selector_setup
|
|
561
|
+
|
|
562
|
+
# Test single panel
|
|
563
|
+
new_ax = Mock()
|
|
564
|
+
new_ax.figure = Mock()
|
|
565
|
+
new_ax.figure.axes = [new_ax]
|
|
566
|
+
|
|
567
|
+
selector.set_ax(new_ax, has_colorbar=False)
|
|
568
|
+
|
|
569
|
+
assert selector._ax is new_ax
|
|
570
|
+
assert selector._is_multipanel is False
|
|
571
|
+
|
|
572
|
+
# Test multi panel with colorbar
|
|
573
|
+
new_ax.figure.axes = [new_ax, Mock(), Mock()] # 3 axes
|
|
574
|
+
selector.set_ax(new_ax, has_colorbar=True)
|
|
575
|
+
assert selector._is_multipanel is True
|
|
576
|
+
|
|
577
|
+
def test_start_text_single_panel(self, selector_setup):
|
|
578
|
+
"""Test start_text method for single panel."""
|
|
579
|
+
selector, plotter, ax = selector_setup
|
|
580
|
+
selector._is_multipanel = False
|
|
581
|
+
|
|
582
|
+
# Mock text creation
|
|
583
|
+
mock_text = Mock()
|
|
584
|
+
ax.text = Mock(return_value=mock_text)
|
|
585
|
+
|
|
586
|
+
selector.start_text()
|
|
587
|
+
|
|
588
|
+
# Should call ax.text with appropriate parameters
|
|
589
|
+
ax.text.assert_called()
|
|
590
|
+
call_kwargs = ax.text.call_args[1]
|
|
591
|
+
assert call_kwargs["va"] == "bottom" # Single panel
|
|
592
|
+
|
|
593
|
+
assert selector._text is mock_text
|
|
594
|
+
|
|
595
|
+
def test_start_text_multi_panel(self, selector_setup):
|
|
596
|
+
"""Test start_text method for multi panel."""
|
|
597
|
+
selector, plotter, ax = selector_setup
|
|
598
|
+
selector._is_multipanel = True
|
|
599
|
+
|
|
600
|
+
# Mock text creation
|
|
601
|
+
mock_text = Mock()
|
|
602
|
+
ax.text = Mock(return_value=mock_text)
|
|
603
|
+
|
|
604
|
+
selector.start_text()
|
|
605
|
+
|
|
606
|
+
# Should call ax.text with appropriate parameters
|
|
607
|
+
ax.text.assert_called()
|
|
608
|
+
call_kwargs = ax.text.call_args[1]
|
|
609
|
+
assert call_kwargs["va"] == "top" # Multi panel
|
|
610
|
+
|
|
611
|
+
assert selector._text is mock_text
|
|
612
|
+
|
|
613
|
+
def test_start_text_custom_kwargs(self, selector_setup):
|
|
614
|
+
"""Test start_text method with custom kwargs."""
|
|
615
|
+
selector, plotter, ax = selector_setup
|
|
616
|
+
|
|
617
|
+
# Mock text creation
|
|
618
|
+
mock_text = Mock()
|
|
619
|
+
ax.text = Mock(return_value=mock_text)
|
|
620
|
+
|
|
621
|
+
custom_kwargs = {"fontsize": 14, "color": "blue"}
|
|
622
|
+
selector.start_text(**custom_kwargs)
|
|
623
|
+
|
|
624
|
+
ax.text.assert_called()
|
|
625
|
+
assert selector._text is mock_text
|
|
626
|
+
|
|
627
|
+
def test_start_selector(self, selector_setup):
|
|
628
|
+
"""Test start_selector method."""
|
|
629
|
+
selector, plotter, ax = selector_setup
|
|
630
|
+
|
|
631
|
+
# Should have initialized selector and tracked initial patches
|
|
632
|
+
assert hasattr(selector, "_selector")
|
|
633
|
+
assert hasattr(selector, "_num_initial_patches")
|
|
634
|
+
assert isinstance(selector._num_initial_patches, int)
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
class TestSelectFromPlot2DSampleData:
|
|
638
|
+
"""Test data sampling methods."""
|
|
639
|
+
|
|
640
|
+
@pytest.fixture
|
|
641
|
+
def selector_setup(self):
|
|
642
|
+
"""Set up a SelectFromPlot2D instance for testing."""
|
|
643
|
+
with patch(
|
|
644
|
+
"solarwindpy.plotting.select_data_from_figure.mpl.widgets.RectangleSelector"
|
|
645
|
+
):
|
|
646
|
+
# Create realistic test data
|
|
647
|
+
np.random.seed(42)
|
|
648
|
+
data = pd.DataFrame(
|
|
649
|
+
{
|
|
650
|
+
"x": np.random.uniform(0, 10, 100),
|
|
651
|
+
"y": np.random.uniform(0, 10, 100),
|
|
652
|
+
"z": np.random.uniform(0, 1, 100),
|
|
653
|
+
},
|
|
654
|
+
index=pd.RangeIndex(100),
|
|
655
|
+
)
|
|
656
|
+
|
|
657
|
+
plotter = Mock()
|
|
658
|
+
plotter.data = data
|
|
659
|
+
plotter.log = Mock()
|
|
660
|
+
plotter.log.x = False
|
|
661
|
+
plotter.log.y = False
|
|
662
|
+
|
|
663
|
+
ax = Mock()
|
|
664
|
+
ax.figure = Mock()
|
|
665
|
+
ax.figure.axes = [ax]
|
|
666
|
+
ax.patches = []
|
|
667
|
+
ax.transAxes = Mock()
|
|
668
|
+
ax.text = Mock(return_value=Mock())
|
|
669
|
+
ax.xaxis = Mock()
|
|
670
|
+
ax.xaxis.get_label = Mock()
|
|
671
|
+
ax.xaxis.get_label.return_value = Mock()
|
|
672
|
+
ax.xaxis.get_label.return_value.get_text = Mock(return_value="x-axis")
|
|
673
|
+
ax.yaxis = Mock()
|
|
674
|
+
ax.yaxis.get_label = Mock()
|
|
675
|
+
ax.yaxis.get_label.return_value = Mock()
|
|
676
|
+
ax.yaxis.get_label.return_value.get_text = Mock(return_value="y-axis")
|
|
677
|
+
ax.figure.canvas = Mock()
|
|
678
|
+
ax.figure.canvas.draw_idle = Mock()
|
|
679
|
+
ax.add_patch = Mock()
|
|
680
|
+
ax.scatter = Mock()
|
|
681
|
+
ax.get_xlim = Mock(return_value=(0, 1))
|
|
682
|
+
ax.get_ylim = Mock(return_value=(0, 1))
|
|
683
|
+
ax.set_xlim = Mock()
|
|
684
|
+
ax.set_ylim = Mock()
|
|
685
|
+
|
|
686
|
+
selector = SelectFromPlot2D(plotter, ax)
|
|
687
|
+
return selector, plotter, ax
|
|
688
|
+
|
|
689
|
+
def test_sample_data_basic(self, selector_setup):
|
|
690
|
+
"""Test basic sample_data functionality."""
|
|
691
|
+
selector, plotter, ax = selector_setup
|
|
692
|
+
|
|
693
|
+
# Add a corner that should contain some data
|
|
694
|
+
selector._add_corners((2, 8, 2, 8))
|
|
695
|
+
|
|
696
|
+
selector.sample_data(n=3, random_state=42)
|
|
697
|
+
|
|
698
|
+
# Should have sampled indices
|
|
699
|
+
assert hasattr(selector, "_sampled_indices")
|
|
700
|
+
assert hasattr(selector, "_failed_samples")
|
|
701
|
+
assert hasattr(selector, "_sampled_per_patch")
|
|
702
|
+
assert selector._sampled_per_patch == 3
|
|
703
|
+
assert isinstance(selector._sampled_indices, pd.Index)
|
|
704
|
+
assert len(selector._failed_samples) >= 0
|
|
705
|
+
|
|
706
|
+
def test_sample_data_frac_not_implemented(self, selector_setup):
|
|
707
|
+
"""Test that sample_data with frac raises NotImplementedError."""
|
|
708
|
+
selector, plotter, ax = selector_setup
|
|
709
|
+
|
|
710
|
+
with pytest.raises(NotImplementedError, match="Please use the `n` kwarg"):
|
|
711
|
+
selector.sample_data(frac=0.1)
|
|
712
|
+
|
|
713
|
+
def test_sample_data_with_log_axes(self, selector_setup):
|
|
714
|
+
"""Test sample_data with logarithmic axes."""
|
|
715
|
+
selector, plotter, ax = selector_setup
|
|
716
|
+
|
|
717
|
+
# Set log axes
|
|
718
|
+
plotter.log.x = True
|
|
719
|
+
plotter.log.y = True
|
|
720
|
+
|
|
721
|
+
# Add corner
|
|
722
|
+
selector._add_corners((1, 10, 1, 10)) # Will be log10 transformed
|
|
723
|
+
|
|
724
|
+
selector.sample_data(n=2, random_state=42)
|
|
725
|
+
|
|
726
|
+
# Should complete without error
|
|
727
|
+
assert hasattr(selector, "_sampled_indices")
|
|
728
|
+
|
|
729
|
+
def test_sample_data_empty_region(self, selector_setup):
|
|
730
|
+
"""Test sample_data with region containing no data."""
|
|
731
|
+
selector, plotter, ax = selector_setup
|
|
732
|
+
|
|
733
|
+
# Add corner outside data range
|
|
734
|
+
selector._add_corners((100, 110, 100, 110))
|
|
735
|
+
|
|
736
|
+
selector.sample_data(n=3, random_state=42)
|
|
737
|
+
|
|
738
|
+
# Should have failed samples
|
|
739
|
+
assert len(selector._failed_samples) == 1
|
|
740
|
+
assert selector._failed_samples[0] == (100, 110, 100, 110)
|
|
741
|
+
|
|
742
|
+
def test_sample_data_with_other_selector(self, selector_setup):
|
|
743
|
+
"""Test sample_data with other_SelectFromPlot2D."""
|
|
744
|
+
selector, plotter, ax = selector_setup
|
|
745
|
+
|
|
746
|
+
# Create another selector with already sampled indices
|
|
747
|
+
other_selector = Mock()
|
|
748
|
+
other_selector.sampled_indices = pd.Index([0, 1, 2])
|
|
749
|
+
|
|
750
|
+
# Add corner
|
|
751
|
+
selector._add_corners((0, 10, 0, 10))
|
|
752
|
+
|
|
753
|
+
selector.sample_data(
|
|
754
|
+
n=3, random_state=42, other_SelectFromPlot2D=other_selector
|
|
755
|
+
)
|
|
756
|
+
|
|
757
|
+
# Should complete without error
|
|
758
|
+
assert hasattr(selector, "_sampled_indices")
|
|
759
|
+
|
|
760
|
+
def test_sample_data_with_other_selector_list(self, selector_setup):
|
|
761
|
+
"""Test sample_data with list of other selectors."""
|
|
762
|
+
selector, plotter, ax = selector_setup
|
|
763
|
+
|
|
764
|
+
# Create other selectors
|
|
765
|
+
other1 = Mock()
|
|
766
|
+
other1.sampled_indices = pd.Index([0, 1])
|
|
767
|
+
other2 = Mock()
|
|
768
|
+
other2.sampled_indices = pd.Index([2, 3])
|
|
769
|
+
|
|
770
|
+
# Add corner
|
|
771
|
+
selector._add_corners((0, 10, 0, 10))
|
|
772
|
+
|
|
773
|
+
selector.sample_data(
|
|
774
|
+
n=3, random_state=42, other_SelectFromPlot2D=[other1, other2]
|
|
775
|
+
)
|
|
776
|
+
|
|
777
|
+
# Should complete without error
|
|
778
|
+
assert hasattr(selector, "_sampled_indices")
|
|
779
|
+
|
|
780
|
+
def test_sample_data_insufficient_samples(self, selector_setup):
|
|
781
|
+
"""Test sample_data when not enough samples available without replacement."""
|
|
782
|
+
selector, plotter, ax = selector_setup
|
|
783
|
+
|
|
784
|
+
# Create data with only 2 points in range but request 5
|
|
785
|
+
selector._plotter.data = pd.DataFrame(
|
|
786
|
+
{"x": [5.0, 5.1], "y": [5.0, 5.1], "z": [0.5, 0.6]}, index=[0, 1]
|
|
787
|
+
)
|
|
788
|
+
|
|
789
|
+
# Add corner that contains both points
|
|
790
|
+
selector._add_corners((4.9, 5.2, 4.9, 5.2))
|
|
791
|
+
|
|
792
|
+
# Mock logger module to capture warning
|
|
793
|
+
with patch(
|
|
794
|
+
"solarwindpy.plotting.select_data_from_figure.logging.getLogger"
|
|
795
|
+
) as mock_get_logger:
|
|
796
|
+
mock_logger = Mock()
|
|
797
|
+
mock_get_logger.return_value = mock_logger
|
|
798
|
+
|
|
799
|
+
selector.sample_data(n=5, random_state=42)
|
|
800
|
+
|
|
801
|
+
# Should log warning about sampling with replacement
|
|
802
|
+
mock_logger.warning.assert_called()
|
|
803
|
+
assert "Sample failed without replacement" in str(
|
|
804
|
+
mock_logger.warning.call_args
|
|
805
|
+
)
|
|
806
|
+
|
|
807
|
+
|
|
808
|
+
class TestSelectFromPlot2DDisconnectAndPlotting:
|
|
809
|
+
"""Test disconnect and plotting methods."""
|
|
810
|
+
|
|
811
|
+
@pytest.fixture
|
|
812
|
+
def selector_setup(self):
|
|
813
|
+
"""Set up a SelectFromPlot2D instance for testing."""
|
|
814
|
+
with patch(
|
|
815
|
+
"solarwindpy.plotting.select_data_from_figure.mpl.widgets.RectangleSelector"
|
|
816
|
+
):
|
|
817
|
+
# Create realistic test data
|
|
818
|
+
np.random.seed(42)
|
|
819
|
+
data = pd.DataFrame(
|
|
820
|
+
{
|
|
821
|
+
"x": np.random.uniform(0, 10, 100),
|
|
822
|
+
"y": np.random.uniform(0, 10, 100),
|
|
823
|
+
"z": np.random.uniform(0, 1, 100),
|
|
824
|
+
},
|
|
825
|
+
index=pd.RangeIndex(100),
|
|
826
|
+
)
|
|
827
|
+
|
|
828
|
+
plotter = Mock()
|
|
829
|
+
plotter.data = data
|
|
830
|
+
plotter.log = Mock()
|
|
831
|
+
plotter.log.x = False
|
|
832
|
+
plotter.log.y = False
|
|
833
|
+
|
|
834
|
+
ax = Mock()
|
|
835
|
+
ax.figure = Mock()
|
|
836
|
+
ax.figure.axes = [ax]
|
|
837
|
+
ax.patches = []
|
|
838
|
+
ax.transAxes = Mock()
|
|
839
|
+
ax.text = Mock(return_value=Mock())
|
|
840
|
+
ax.xaxis = Mock()
|
|
841
|
+
ax.xaxis.get_label = Mock()
|
|
842
|
+
ax.xaxis.get_label.return_value = Mock()
|
|
843
|
+
ax.xaxis.get_label.return_value.get_text = Mock(return_value="x-axis")
|
|
844
|
+
ax.yaxis = Mock()
|
|
845
|
+
ax.yaxis.get_label = Mock()
|
|
846
|
+
ax.yaxis.get_label.return_value = Mock()
|
|
847
|
+
ax.yaxis.get_label.return_value.get_text = Mock(return_value="y-axis")
|
|
848
|
+
ax.figure.canvas = Mock()
|
|
849
|
+
ax.figure.canvas.draw_idle = Mock()
|
|
850
|
+
ax.add_patch = Mock()
|
|
851
|
+
ax.scatter = Mock()
|
|
852
|
+
ax.get_xlim = Mock(return_value=(0, 10))
|
|
853
|
+
ax.get_ylim = Mock(return_value=(0, 10))
|
|
854
|
+
ax.set_xlim = Mock()
|
|
855
|
+
ax.set_ylim = Mock()
|
|
856
|
+
|
|
857
|
+
selector = SelectFromPlot2D(plotter, ax)
|
|
858
|
+
|
|
859
|
+
# Set up some sampled data
|
|
860
|
+
selector._add_corners((2, 8, 2, 8))
|
|
861
|
+
selector.sample_data(n=3, random_state=42)
|
|
862
|
+
|
|
863
|
+
return selector, plotter, ax
|
|
864
|
+
|
|
865
|
+
def test_disconnect(self, selector_setup):
|
|
866
|
+
"""Test disconnect method."""
|
|
867
|
+
selector, plotter, ax = selector_setup
|
|
868
|
+
|
|
869
|
+
# Mock selector disconnect
|
|
870
|
+
mock_selector = Mock()
|
|
871
|
+
mock_selector.disconnect_events = Mock()
|
|
872
|
+
selector._selector = mock_selector
|
|
873
|
+
|
|
874
|
+
# Mock text object
|
|
875
|
+
mock_text = Mock()
|
|
876
|
+
selector._text = mock_text
|
|
877
|
+
|
|
878
|
+
selector.disconnect()
|
|
879
|
+
|
|
880
|
+
# Should call all required methods
|
|
881
|
+
mock_selector.disconnect_events.assert_called_once()
|
|
882
|
+
mock_text.set_text.assert_called()
|
|
883
|
+
|
|
884
|
+
def test_disconnect_with_other_selector(self, selector_setup):
|
|
885
|
+
"""Test disconnect with other SelectFromPlot2D."""
|
|
886
|
+
selector, plotter, ax = selector_setup
|
|
887
|
+
|
|
888
|
+
# Create other selector
|
|
889
|
+
other_selector = Mock()
|
|
890
|
+
other_selector.sampled_indices = pd.Index([10, 11, 12])
|
|
891
|
+
|
|
892
|
+
# Mock selector disconnect
|
|
893
|
+
mock_selector = Mock()
|
|
894
|
+
mock_selector.disconnect_events = Mock()
|
|
895
|
+
selector._selector = mock_selector
|
|
896
|
+
|
|
897
|
+
# Mock text object
|
|
898
|
+
mock_text = Mock()
|
|
899
|
+
selector._text = mock_text
|
|
900
|
+
|
|
901
|
+
selector.disconnect(other_SelectFromPlot2D=other_selector)
|
|
902
|
+
|
|
903
|
+
# Should call disconnect_events
|
|
904
|
+
mock_selector.disconnect_events.assert_called_once()
|
|
905
|
+
|
|
906
|
+
def test_disconnect_with_scatter_kwargs(self, selector_setup):
|
|
907
|
+
"""Test disconnect with custom scatter kwargs."""
|
|
908
|
+
selector, plotter, ax = selector_setup
|
|
909
|
+
|
|
910
|
+
# Mock selector disconnect
|
|
911
|
+
mock_selector = Mock()
|
|
912
|
+
mock_selector.disconnect_events = Mock()
|
|
913
|
+
selector._selector = mock_selector
|
|
914
|
+
|
|
915
|
+
# Mock text object
|
|
916
|
+
mock_text = Mock()
|
|
917
|
+
selector._text = mock_text
|
|
918
|
+
|
|
919
|
+
scatter_kwargs = {"color": "red", "s": 50}
|
|
920
|
+
selector.disconnect(scatter_kwargs=scatter_kwargs)
|
|
921
|
+
|
|
922
|
+
# Should call disconnect_events
|
|
923
|
+
mock_selector.disconnect_events.assert_called_once()
|
|
924
|
+
|
|
925
|
+
def test_scatter_sample(self, selector_setup):
|
|
926
|
+
"""Test scatter_sample method."""
|
|
927
|
+
selector, plotter, ax = selector_setup
|
|
928
|
+
|
|
929
|
+
selector.scatter_sample()
|
|
930
|
+
|
|
931
|
+
# Should call ax.scatter
|
|
932
|
+
ax.scatter.assert_called_once()
|
|
933
|
+
|
|
934
|
+
# Should preserve axis limits
|
|
935
|
+
ax.set_xlim.assert_called_once_with(0, 10)
|
|
936
|
+
ax.set_ylim.assert_called_once_with(0, 10)
|
|
937
|
+
|
|
938
|
+
def test_scatter_sample_with_log_axes(self, selector_setup):
|
|
939
|
+
"""Test scatter_sample with logarithmic axes."""
|
|
940
|
+
selector, plotter, ax = selector_setup
|
|
941
|
+
|
|
942
|
+
# Set log axes
|
|
943
|
+
plotter.log.x = True
|
|
944
|
+
plotter.log.y = True
|
|
945
|
+
|
|
946
|
+
selector.scatter_sample()
|
|
947
|
+
|
|
948
|
+
# Should call ax.scatter (data will be transformed)
|
|
949
|
+
ax.scatter.assert_called_once()
|
|
950
|
+
|
|
951
|
+
def test_scatter_sample_custom_kwargs(self, selector_setup):
|
|
952
|
+
"""Test scatter_sample with custom kwargs."""
|
|
953
|
+
selector, plotter, ax = selector_setup
|
|
954
|
+
|
|
955
|
+
custom_kwargs = {"c": "blue", "s": 100, "marker": "x"}
|
|
956
|
+
selector.scatter_sample(**custom_kwargs)
|
|
957
|
+
|
|
958
|
+
# Should call ax.scatter
|
|
959
|
+
ax.scatter.assert_called_once()
|
|
960
|
+
call_kwargs = ax.scatter.call_args[1]
|
|
961
|
+
assert call_kwargs["c"] == "blue"
|
|
962
|
+
assert call_kwargs["s"] == 100
|
|
963
|
+
assert call_kwargs["marker"] == "x"
|
|
964
|
+
|
|
965
|
+
def test_plot_failed_samples(self, selector_setup):
|
|
966
|
+
"""Test plot_failed_samples method."""
|
|
967
|
+
selector, plotter, ax = selector_setup
|
|
968
|
+
|
|
969
|
+
# Add some failed samples
|
|
970
|
+
selector._failed_samples = [(100, 110, 100, 110), (120, 130, 120, 130)]
|
|
971
|
+
|
|
972
|
+
selector.plot_failed_samples()
|
|
973
|
+
|
|
974
|
+
# Should add patches for failed samples
|
|
975
|
+
assert ax.add_patch.call_count == 2
|
|
976
|
+
ax.figure.canvas.draw_idle.assert_called_once()
|
|
977
|
+
|
|
978
|
+
def test_plot_failed_samples_empty(self, selector_setup):
|
|
979
|
+
"""Test plot_failed_samples with no failed samples."""
|
|
980
|
+
selector, plotter, ax = selector_setup
|
|
981
|
+
|
|
982
|
+
# No failed samples
|
|
983
|
+
selector._failed_samples = []
|
|
984
|
+
|
|
985
|
+
selector.plot_failed_samples()
|
|
986
|
+
|
|
987
|
+
# Should still call draw_idle but no patches added
|
|
988
|
+
ax.figure.canvas.draw_idle.assert_called_once()
|
|
989
|
+
# ax.add_patch should not be called
|
|
990
|
+
ax.add_patch.assert_not_called()
|
|
991
|
+
|
|
992
|
+
|
|
993
|
+
class TestSelectFromPlot2DDateAxes:
|
|
994
|
+
"""Test date axes functionality."""
|
|
995
|
+
|
|
996
|
+
@pytest.fixture
|
|
997
|
+
def selector_setup(self):
|
|
998
|
+
"""Set up a SelectFromPlot2D instance for testing."""
|
|
999
|
+
with patch(
|
|
1000
|
+
"solarwindpy.plotting.select_data_from_figure.mpl.widgets.RectangleSelector"
|
|
1001
|
+
):
|
|
1002
|
+
plotter = Mock()
|
|
1003
|
+
plotter.data = pd.DataFrame(
|
|
1004
|
+
{
|
|
1005
|
+
"x": np.random.randn(100),
|
|
1006
|
+
"y": np.random.randn(100),
|
|
1007
|
+
"z": np.random.randn(100),
|
|
1008
|
+
}
|
|
1009
|
+
)
|
|
1010
|
+
plotter.log = Mock()
|
|
1011
|
+
plotter.log.x = False
|
|
1012
|
+
plotter.log.y = False
|
|
1013
|
+
|
|
1014
|
+
ax = Mock()
|
|
1015
|
+
ax.figure = Mock()
|
|
1016
|
+
ax.figure.axes = [ax]
|
|
1017
|
+
ax.patches = []
|
|
1018
|
+
ax.transAxes = Mock()
|
|
1019
|
+
ax.text = Mock(return_value=Mock())
|
|
1020
|
+
ax.xaxis = Mock()
|
|
1021
|
+
ax.xaxis.get_label = Mock()
|
|
1022
|
+
ax.xaxis.get_label.return_value = Mock()
|
|
1023
|
+
ax.xaxis.get_label.return_value.get_text = Mock(return_value="x-axis")
|
|
1024
|
+
ax.yaxis = Mock()
|
|
1025
|
+
ax.yaxis.get_label = Mock()
|
|
1026
|
+
ax.yaxis.get_label.return_value = Mock()
|
|
1027
|
+
ax.yaxis.get_label.return_value.get_text = Mock(return_value="y-axis")
|
|
1028
|
+
ax.figure.canvas = Mock()
|
|
1029
|
+
ax.figure.canvas.draw_idle = Mock()
|
|
1030
|
+
|
|
1031
|
+
selector = SelectFromPlot2D(plotter, ax)
|
|
1032
|
+
return selector, plotter, ax
|
|
1033
|
+
|
|
1034
|
+
def test_set_date_axes(self, selector_setup):
|
|
1035
|
+
"""Test set_date_axes method."""
|
|
1036
|
+
selector, plotter, ax = selector_setup
|
|
1037
|
+
|
|
1038
|
+
selector.set_date_axes(True, False)
|
|
1039
|
+
|
|
1040
|
+
assert selector.date_axes.x is True
|
|
1041
|
+
assert selector.date_axes.y is False
|
|
1042
|
+
|
|
1043
|
+
selector.set_date_axes(False, True)
|
|
1044
|
+
|
|
1045
|
+
assert selector.date_axes.x is False
|
|
1046
|
+
assert selector.date_axes.y is True
|
|
1047
|
+
|
|
1048
|
+
|
|
1049
|
+
class TestSelectFromPlot2DEdgeCases:
|
|
1050
|
+
"""Test edge cases and error handling."""
|
|
1051
|
+
|
|
1052
|
+
@pytest.fixture
|
|
1053
|
+
def selector_setup(self):
|
|
1054
|
+
"""Set up a SelectFromPlot2D instance for testing."""
|
|
1055
|
+
with patch(
|
|
1056
|
+
"solarwindpy.plotting.select_data_from_figure.mpl.widgets.RectangleSelector"
|
|
1057
|
+
):
|
|
1058
|
+
plotter = Mock()
|
|
1059
|
+
plotter.data = pd.DataFrame(
|
|
1060
|
+
{
|
|
1061
|
+
"x": np.random.randn(100),
|
|
1062
|
+
"y": np.random.randn(100),
|
|
1063
|
+
"z": np.random.randn(100),
|
|
1064
|
+
}
|
|
1065
|
+
)
|
|
1066
|
+
plotter.log = Mock()
|
|
1067
|
+
plotter.log.x = False
|
|
1068
|
+
plotter.log.y = False
|
|
1069
|
+
|
|
1070
|
+
ax = Mock()
|
|
1071
|
+
ax.figure = Mock()
|
|
1072
|
+
ax.figure.axes = [ax]
|
|
1073
|
+
ax.patches = []
|
|
1074
|
+
ax.transAxes = Mock()
|
|
1075
|
+
ax.text = Mock(return_value=Mock())
|
|
1076
|
+
ax.xaxis = Mock()
|
|
1077
|
+
ax.xaxis.get_label = Mock()
|
|
1078
|
+
ax.xaxis.get_label.return_value = Mock()
|
|
1079
|
+
ax.xaxis.get_label.return_value.get_text = Mock(return_value="x-axis")
|
|
1080
|
+
ax.yaxis = Mock()
|
|
1081
|
+
ax.yaxis.get_label = Mock()
|
|
1082
|
+
ax.yaxis.get_label.return_value = Mock()
|
|
1083
|
+
ax.yaxis.get_label.return_value.get_text = Mock(return_value="y-axis")
|
|
1084
|
+
ax.figure.canvas = Mock()
|
|
1085
|
+
ax.figure.canvas.draw_idle = Mock()
|
|
1086
|
+
|
|
1087
|
+
selector = SelectFromPlot2D(plotter, ax)
|
|
1088
|
+
return selector, plotter, ax
|
|
1089
|
+
|
|
1090
|
+
def test_sample_data_value_error_reraise(self, selector_setup):
|
|
1091
|
+
"""Test that ValueError other than sample size is re-raised."""
|
|
1092
|
+
selector, plotter, ax = selector_setup
|
|
1093
|
+
|
|
1094
|
+
# Mock pandas Series.sample to raise different ValueError
|
|
1095
|
+
with patch("pandas.Series.sample") as mock_sample:
|
|
1096
|
+
mock_sample.side_effect = ValueError("Different error message")
|
|
1097
|
+
|
|
1098
|
+
selector._add_corners((0, 10, 0, 10))
|
|
1099
|
+
|
|
1100
|
+
with pytest.raises(ValueError, match="Different error message"):
|
|
1101
|
+
selector.sample_data(n=3, random_state=42)
|
|
1102
|
+
|
|
1103
|
+
def test_sample_data_missing_already_selected(self, selector_setup):
|
|
1104
|
+
"""Test sample_data when already_selected indices are missing."""
|
|
1105
|
+
selector, plotter, ax = selector_setup
|
|
1106
|
+
|
|
1107
|
+
# Create other selector with indices not in current data
|
|
1108
|
+
other_selector = Mock()
|
|
1109
|
+
other_selector.sampled_indices = pd.Index(
|
|
1110
|
+
[200, 201, 202]
|
|
1111
|
+
) # Outside current data range
|
|
1112
|
+
|
|
1113
|
+
# Add corner
|
|
1114
|
+
selector._add_corners((0, 10, 0, 10))
|
|
1115
|
+
|
|
1116
|
+
# Mock logger module to capture warning
|
|
1117
|
+
with patch(
|
|
1118
|
+
"solarwindpy.plotting.select_data_from_figure.logging.getLogger"
|
|
1119
|
+
) as mock_get_logger:
|
|
1120
|
+
mock_logger = Mock()
|
|
1121
|
+
mock_get_logger.return_value = mock_logger
|
|
1122
|
+
|
|
1123
|
+
selector.sample_data(
|
|
1124
|
+
n=3, random_state=42, other_SelectFromPlot2D=other_selector
|
|
1125
|
+
)
|
|
1126
|
+
|
|
1127
|
+
# Should log warning about missing indices
|
|
1128
|
+
mock_logger.warning.assert_called()
|
|
1129
|
+
assert "None of `already_selected` found" in str(
|
|
1130
|
+
mock_logger.warning.call_args
|
|
1131
|
+
)
|
|
1132
|
+
|
|
1133
|
+
def test_sample_data_other_selector_no_sampled_indices(self, selector_setup):
|
|
1134
|
+
"""Test sample_data with other selector that has no sampled_indices
|
|
1135
|
+
attribute."""
|
|
1136
|
+
selector, plotter, ax = selector_setup
|
|
1137
|
+
|
|
1138
|
+
# Create other selector without sampled_indices
|
|
1139
|
+
other_selector = Mock()
|
|
1140
|
+
del other_selector.sampled_indices # Remove attribute
|
|
1141
|
+
|
|
1142
|
+
# Add corner
|
|
1143
|
+
selector._add_corners((0, 10, 0, 10))
|
|
1144
|
+
|
|
1145
|
+
# Should handle gracefully
|
|
1146
|
+
selector.sample_data(
|
|
1147
|
+
n=3, random_state=42, other_SelectFromPlot2D=other_selector
|
|
1148
|
+
)
|
|
1149
|
+
|
|
1150
|
+
# Should complete without error
|
|
1151
|
+
assert hasattr(selector, "_sampled_indices")
|
|
1152
|
+
|
|
1153
|
+
|
|
1154
|
+
class TestSelectFromPlot2DIntegration:
|
|
1155
|
+
"""Integration tests for SelectFromPlot2D."""
|
|
1156
|
+
|
|
1157
|
+
@patch("solarwindpy.plotting.select_data_from_figure.mpl.widgets.RectangleSelector")
|
|
1158
|
+
def test_full_workflow(self, mock_rect_selector):
|
|
1159
|
+
"""Test complete selection workflow."""
|
|
1160
|
+
# Set up realistic scenario
|
|
1161
|
+
np.random.seed(42)
|
|
1162
|
+
data = pd.DataFrame(
|
|
1163
|
+
{
|
|
1164
|
+
"x": np.random.uniform(0, 10, 50),
|
|
1165
|
+
"y": np.random.uniform(0, 10, 50),
|
|
1166
|
+
"z": np.random.uniform(0, 1, 50),
|
|
1167
|
+
},
|
|
1168
|
+
index=pd.RangeIndex(50),
|
|
1169
|
+
)
|
|
1170
|
+
|
|
1171
|
+
plotter = Mock()
|
|
1172
|
+
plotter.data = data
|
|
1173
|
+
plotter.log = Mock()
|
|
1174
|
+
plotter.log.x = False
|
|
1175
|
+
plotter.log.y = False
|
|
1176
|
+
|
|
1177
|
+
# Create figure and axis
|
|
1178
|
+
fig, ax = plt.subplots()
|
|
1179
|
+
|
|
1180
|
+
# Mock selector
|
|
1181
|
+
mock_selector = Mock()
|
|
1182
|
+
mock_selector.extents = (2, 8, 2, 8)
|
|
1183
|
+
mock_selector._rect_bbox = [2, 2, 6, 6]
|
|
1184
|
+
mock_selector.disconnect_events = Mock()
|
|
1185
|
+
mock_rect_selector.return_value = mock_selector
|
|
1186
|
+
|
|
1187
|
+
# Create selector
|
|
1188
|
+
selector = SelectFromPlot2D(plotter, ax)
|
|
1189
|
+
|
|
1190
|
+
# Simulate selection
|
|
1191
|
+
selector.onselect("press", "release")
|
|
1192
|
+
|
|
1193
|
+
# Should have added corner
|
|
1194
|
+
assert len(selector.corners) == 1
|
|
1195
|
+
|
|
1196
|
+
# Sample data
|
|
1197
|
+
selector.sample_data(n=3, random_state=42)
|
|
1198
|
+
|
|
1199
|
+
# Should have sampled indices
|
|
1200
|
+
assert hasattr(selector, "_sampled_indices")
|
|
1201
|
+
assert len(selector._sampled_indices) > 0
|
|
1202
|
+
|
|
1203
|
+
# Disconnect
|
|
1204
|
+
selector.disconnect()
|
|
1205
|
+
|
|
1206
|
+
# Should have called disconnect_events
|
|
1207
|
+
mock_selector.disconnect_events.assert_called_once()
|
|
1208
|
+
|
|
1209
|
+
plt.close(fig)
|