Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature #461 hovmoeller tests #466

Merged
merged 4 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions metplotpy/plots/config/scatter_defaults.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,8 @@ scatters:
color: black
width: 1
dash:

log_filename: stdout
log_level: ERROR

line_type: N/A
1 change: 1 addition & 0 deletions metplotpy/plots/hovmoeller/hovmoeller_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,4 @@ def __init__(self, parameters):
self.colorscale = self.get_config_value('colorscale')
self.xaxis = self.get_config_value('xaxis')
self.yaxis = self.get_config_value('yaxis')
self.create_html = self.get_config_value('create_html')
7 changes: 3 additions & 4 deletions metplotpy/plots/scatter/scatter.py
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This version of the scatter plot is no longer useful, it has been refactored, using matplotlib and is pending further instructions. Work is in the feature_23_scatter feature branch, and user documentation on read the docs: https://metplotpy.readthedocs.io/en/feature_23_scatter/Users_Guide/scatter.html

The new scatter plot's imports are consistent with the suggested changes

Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,8 @@
__author__ = 'Hank Fisher'

import plotly.graph_objects as go
import yaml
import pandas as pd
from plots.base_plot import BasePlot
from metplotpy.plots.base_plot import BasePlot

from metplotpy.plots import util

Expand Down Expand Up @@ -50,7 +49,7 @@ def __repr__(self):
class.
"""

return f'Line({self.parameters!r})'
return f'Scatter ({self.parameters!r})'

def _get_all_scatters(self):
""" Retrieve a list of all scatters. Each scatters is a dictionary comprised of
Expand Down Expand Up @@ -112,7 +111,7 @@ def _create_figure(self):
scatter_x = data['x']
scatter_y = data['y']
fig.add_trace(go.Scatter(
x=scatter_x, y=scatter_y, name=name
x=scatter_x, y=scatter_y, name=name, connectgaps = connect_gap, mode = "markers"
))


Expand Down
8 changes: 7 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,10 @@ find = {include = ["metplotpy*"]}
testpaths = ["test"]

[tool.coverage.run]
source = ["metplotpy/plots"]
source = ["metplotpy/plots"]

[tool.coverage.report]
exclude_also = [
"def __repr__",
"if __name__ == .__main__.:",
]
77 changes: 77 additions & 0 deletions test/conftest.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,52 @@
import pytest
import os
import shutil
import json
import xarray as xr
from pandas import DatetimeIndex

# This fixture temporarily sets the working directory
# to the dir containing the test file. This means
# realative file locations can be used for each test
# file.
# NOTE: autouse=True means this applies to ALL tests.
# Code that updates the cwd inside a test file is now
# redundant and can be deleted.
@pytest.fixture(autouse=True)
def change_test_dir(request, monkeypatch):
monkeypatch.chdir(request.fspath.dirname)


def ordered(obj):
"""Recursive function to sort JSON, even lists of dicts with the same keys"""
if isinstance(obj, dict):
return sorted((k, ordered(v)) for k, v in obj.items())
if isinstance(obj, list):
return sorted(ordered(x) for x in obj)
else:
return obj


@pytest.fixture
def assert_json_equal():
def compare_json(fig, expected_json_file):
"""Takes a plotly figure and a json file
"""
# Treat everything as str for comparison purposes.
actual = json.loads(fig.to_json(), parse_float=str, parse_int=str)
with open(expected_json_file) as f:
expected = json.load(f,parse_float=str, parse_int=str)

# Fail with a nice message
if ordered(actual) == ordered(expected):
return True
else:
message = "This test will fail when there have been changes to plot code but the corresponding" \
"json test file hasn't been updates. To update the test file run `fig.write_json`"\
" e.g. `scatter.figure.write_json('custom_scatter_expected.json')`"
raise AssertionError(message)

return compare_json


@pytest.fixture
Expand Down Expand Up @@ -31,3 +77,34 @@ def remove_the_files(test_dir, file_list):
pass

return remove_the_files


TEST_NC_DATA = xr.Dataset(
{
"precip": xr.DataArray(
[
[[0.1, 0.2, 0.3], [0, 1.3, 4], [0, 20, 0]],
[[0, 0, 0], [0, 0, 0], [0, 0, 0]],
],
coords={
"lat": [-1, 0, 1],
"lon": [112, 113, 114],
"time": DatetimeIndex(["2024-09-25 00:00:00", "2024-09-25 03:00:33"]),
},
dims=["time", "lat", "lon"],
attrs={"long_name": "variable long name"},
),
},
attrs={"Conventions": "CF-99.9", "history": "History string"},
)

@pytest.fixture()
def nc_test_file(tmp_path_factory):
"""Create a netCDF file with a very small amount of data.
File is written to a temp directory and the path to the
file returned as the fixture value.
"""
file_name = tmp_path_factory.mktemp("data") / "test_data.nc"
TEST_NC_DATA.to_netcdf(file_name)
return file_name

1 change: 1 addition & 0 deletions test/hovmoeller/hovmoeller_test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"data":[{"colorbar":{"len":0.6,"lenmode":"fraction","title":{"text":"mm / day"}},"colorscale":[[0.0,"rgb(247,252,253)"],[0.125,"rgb(224,236,244)"],[0.25,"rgb(191,211,230)"],[0.375,"rgb(158,188,218)"],[0.5,"rgb(140,150,198)"],[0.625,"rgb(140,107,177)"],[0.75,"rgb(136,65,157)"],[0.875,"rgb(129,15,124)"],[1.0,"rgb(77,0,75)"]],"contours":{"end":10,"showlines":false,"size":0.2,"start":0.1},"x":[112,113,114],"y":["2024-09-25 00:00","2024-09-25 03:00"],"z":[[8.333333333333334,1791.6666666666667,358.3333333333333],[0.0,0.0,0.0]],"type":"contour"}],"layout":{"template":{"data":{"histogram2dcontour":[{"type":"histogram2dcontour","colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]}],"choropleth":[{"type":"choropleth","colorbar":{"outlinewidth":0,"ticks":""}}],"histogram2d":[{"type":"histogram2d","colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]}],"heatmap":[{"type":"heatmap","colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]}],"heatmapgl":[{"type":"heatmapgl","colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]}],"contourcarpet":[{"type":"contourcarpet","colorbar":{"outlinewidth":0,"ticks":""}}],"contour":[{"type":"contour","colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]}],"surface":[{"type":"surface","colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]}],"mesh3d":[{"type":"mesh3d","colorbar":{"outlinewidth":0,"ticks":""}}],"scatter":[{"fillpattern":{"fillmode":"overlay","size":10,"solidity":0.2},"type":"scatter"}],"parcoords":[{"type":"parcoords","line":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"scatterpolargl":[{"type":"scatterpolargl","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"bar":[{"error_x":{"color":"#2a3f5f"},"error_y":{"color":"#2a3f5f"},"marker":{"line":{"color":"#E5ECF6","width":0.5},"pattern":{"fillmode":"overlay","size":10,"solidity":0.2}},"type":"bar"}],"scattergeo":[{"type":"scattergeo","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"scatterpolar":[{"type":"scatterpolar","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"histogram":[{"marker":{"pattern":{"fillmode":"overlay","size":10,"solidity":0.2}},"type":"histogram"}],"scattergl":[{"type":"scattergl","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"scatter3d":[{"type":"scatter3d","line":{"colorbar":{"outlinewidth":0,"ticks":""}},"marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"scattermapbox":[{"type":"scattermapbox","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"scatterternary":[{"type":"scatterternary","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"scattercarpet":[{"type":"scattercarpet","marker":{"colorbar":{"outlinewidth":0,"ticks":""}}}],"carpet":[{"aaxis":{"endlinecolor":"#2a3f5f","gridcolor":"white","linecolor":"white","minorgridcolor":"white","startlinecolor":"#2a3f5f"},"baxis":{"endlinecolor":"#2a3f5f","gridcolor":"white","linecolor":"white","minorgridcolor":"white","startlinecolor":"#2a3f5f"},"type":"carpet"}],"table":[{"cells":{"fill":{"color":"#EBF0F8"},"line":{"color":"white"}},"header":{"fill":{"color":"#C8D4E3"},"line":{"color":"white"}},"type":"table"}],"barpolar":[{"marker":{"line":{"color":"#E5ECF6","width":0.5},"pattern":{"fillmode":"overlay","size":10,"solidity":0.2}},"type":"barpolar"}],"pie":[{"automargin":true,"type":"pie"}]},"layout":{"autotypenumbers":"strict","colorway":["#636efa","#EF553B","#00cc96","#ab63fa","#FFA15A","#19d3f3","#FF6692","#B6E880","#FF97FF","#FECB52"],"font":{"color":"#2a3f5f"},"hovermode":"closest","hoverlabel":{"align":"left"},"paper_bgcolor":"white","plot_bgcolor":"#E5ECF6","polar":{"bgcolor":"#E5ECF6","angularaxis":{"gridcolor":"white","linecolor":"white","ticks":""},"radialaxis":{"gridcolor":"white","linecolor":"white","ticks":""}},"ternary":{"bgcolor":"#E5ECF6","aaxis":{"gridcolor":"white","linecolor":"white","ticks":""},"baxis":{"gridcolor":"white","linecolor":"white","ticks":""},"caxis":{"gridcolor":"white","linecolor":"white","ticks":""}},"coloraxis":{"colorbar":{"outlinewidth":0,"ticks":""}},"colorscale":{"sequential":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"sequentialminus":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"diverging":[[0,"#8e0152"],[0.1,"#c51b7d"],[0.2,"#de77ae"],[0.3,"#f1b6da"],[0.4,"#fde0ef"],[0.5,"#f7f7f7"],[0.6,"#e6f5d0"],[0.7,"#b8e186"],[0.8,"#7fbc41"],[0.9,"#4d9221"],[1,"#276419"]]},"xaxis":{"gridcolor":"white","linecolor":"white","ticks":"","title":{"standoff":15},"zerolinecolor":"white","automargin":true,"zerolinewidth":2},"yaxis":{"gridcolor":"white","linecolor":"white","ticks":"","title":{"standoff":15},"zerolinecolor":"white","automargin":true,"zerolinewidth":2},"scene":{"xaxis":{"backgroundcolor":"#E5ECF6","gridcolor":"white","linecolor":"white","showbackground":true,"ticks":"","zerolinecolor":"white","gridwidth":2},"yaxis":{"backgroundcolor":"#E5ECF6","gridcolor":"white","linecolor":"white","showbackground":true,"ticks":"","zerolinecolor":"white","gridwidth":2},"zaxis":{"backgroundcolor":"#E5ECF6","gridcolor":"white","linecolor":"white","showbackground":true,"ticks":"","zerolinecolor":"white","gridwidth":2}},"shapedefaults":{"line":{"color":"#2a3f5f"}},"annotationdefaults":{"arrowcolor":"#2a3f5f","arrowhead":0,"arrowwidth":1},"geo":{"bgcolor":"white","landcolor":"#E5ECF6","subunitcolor":"white","showland":true,"showlakes":true,"lakecolor":"white"},"title":{"x":0.05},"mapbox":{"style":"light"}}},"font":{"size":20},"title":{"text":"test plot 5S - 5N","font":{"size":20}},"height":800,"width":1200,"xaxis":{"title":{"text":"Longitude"}},"yaxis":{"title":{"text":"Time"}}}}
78 changes: 68 additions & 10 deletions test/hovmoeller/test_hovmoeller.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
import os
import pytest
import metplotpy.plots.hovmoeller.hovmoeller as hov
from metplotpy.plots import util
#from metcalcpy.compare_images import CompareImages

def dict_to_yaml(data_dict,
output_yaml = "test_hovmoeller.yaml"):
"""Write dict as yaml config file."""
content = "\n".join(["{k}: {v}".format(k=k,v=v) for k,v in data_dict.items()])
with open(output_yaml, 'w') as f:
f.write(content)
return output_yaml


def cleanup(file_to_remove):
try:
path = os.getcwd()
Expand All @@ -13,16 +23,6 @@ def cleanup(file_to_remove):
pass


@pytest.mark.skip("needs large netCDF file to run")
def test_default_plot_created():
config_file = os.path.join(os.path.dirname(__file__), "minimal_hovmoeller.yaml")
hov.main(config_file)
default_plot = "./hovmoeller_default_plot.png"
assert os.path.isfile(default_plot) == True

# Clean up
cleanup(default_plot)

@pytest.mark.skip()
def test_default_plot_images_match():
'''
Expand Down Expand Up @@ -61,3 +61,61 @@ def test_custom_plot_created():

# Clean up
cleanup(custom_plot)


def make_config(nc_file, out_file):

# values here are sensitive to those set
# in the `nc_test_file` fixture.
config = {
"input_data_file": nc_file,
"plot_filename": out_file,
"date_start": "2024-09-25",
"date_end": "2024-09-26",
"contour_min": 0.1,
"contour_max": 10,
"unit_converion": 1,
"title": "test plot",
"create_html": "true",
}
return config

def test_hovmoeller(nc_test_file,assert_json_equal):
out_file = "hovmoeller_test.png"
config = make_config(nc_test_file, out_file)

# basic test to see if output writes
min_yaml = dict_to_yaml(config)
hov.main(min_yaml)

assert os.path.isfile(out_file)

# test actual functions from plot object
plot_obj = hov.Hovmoeller(util.get_params(min_yaml))

# check html write out
plot_obj.write_html()
out_html = config['plot_filename'].split('.')[0] + '.html'
assert os.path.isfile(out_html)

# finally check json plot values
# to regenerate json file run:
# plot_obj.figure.write_json('hovmoeller_test.json')
assert_json_equal(plot_obj.figure, 'hovmoeller_test.json')

# Clean up
cleanup(out_file)
cleanup(out_html)

def test_get_lat_str(nc_test_file):
min_yaml = dict_to_yaml(make_config(nc_test_file, "test.png"))
plot_obj = hov.Hovmoeller(util.get_params(min_yaml))

actual = plot_obj.get_lat_str(-4,-2)
assert actual == "4S - 2S"

actual = plot_obj.get_lat_str(-4,12)
assert actual == "4S - 12N"

actual = plot_obj.get_lat_str(23,90)
assert actual == "23N - 90N"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this won't be necessary, a new config file corresponding to the refactored scatter plot will replace this config file

Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ legend:
bordercolor: # black in default config
borderwidth: 2

connect_data_gaps: true
connect_data_gaps: false
title: Scatter plot of point data
xaxis:
title: X
yaxis:
title: accumulated precip amounts (cm)
lines:
scatter:
- name: Data1 Trace
data_file:
./scatter1_plot_data.txt
Expand All @@ -33,3 +33,7 @@ lines:
width: 4
dash: dash

log_filename: stdout
log_level: ERROR

line_type: N/A
Loading