Skip to content

Commit

Permalink
Feature #461 hovmoeller tests (#466)
Browse files Browse the repository at this point in the history
* 461: scatter tests and json compare

* 461: tidy up docs and whitespace

* 461: remove accidental git add

* 461: hovmoeller test updates
  • Loading branch information
John-Sharples authored Sep 26, 2024
1 parent c11c25c commit b9c5c15
Show file tree
Hide file tree
Showing 13 changed files with 182 additions and 17 deletions.
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
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"
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

0 comments on commit b9c5c15

Please sign in to comment.