Skip to content
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
4 changes: 2 additions & 2 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ authors:
repository-code: https://github.com/JuliaAstro/SolarPosition.jl
url: https://juliaastro.github.io/SolarPosition.jl/
license: MIT
version: 0.4.0
date-released: 2026-01-05
version: 0.4.1
date-released: 2026-01-17
abstract: >-
SolarPosition.jl provides a simple, unified interface to a collection
of validated solar position algorithms written in pure Julia.
Expand Down
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "SolarPosition"
uuid = "5b9d1343-a731-5a90-8730-7bf8d89bf3eb"
version = "0.4.0"
version = "0.4.1"
authors = ["Stefan de Lange"]

[workspace]
Expand Down
1 change: 1 addition & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ makedocs(;
"literature.md",
"contributing.md",
],
draft = true,
)

deploydocs(;
Expand Down
75 changes: 41 additions & 34 deletions docs/src/guides/plotting.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
```@meta
Draft = false
```

# [Plotting with Makie.jl](@id plotting-examples)

SolarPosition.jl provides a plotting extension for [Makie.jl](https://makie.juliaplots.org/stable/).

The main plotting function is [`analemmas!`](@ref).

To use it, simply import both the `SolarPosition` and `Makie` packages:

```@example plotting
Expand All @@ -11,72 +17,61 @@ using CairoMakie
# supporting packages
using Dates
using TimeZones
using DataFrames
```

This example notebook is based on the [pvlib sun path example](https://pvlib-python.readthedocs.io/en/stable/gallery/solar-position/plot_sunpath_diagrams.html).

## Basic Sun Path Plotting

Let's start by defining an observer location and calculating solar positions for a whole year:
The plotting functions generate analemmas (figure-8 patterns showing the sun's position at
each hour of the day throughout the year). You simply provide an observer location and
the year you want to visualize:

```@example plotting
# Define observer location (New Delhi, India)
# Parameters: latitude, longitude, altitude in meters
tz = tz"Asia/Kolkata"
obs = Observer(28.6, 77.2, 0.0)

# Generate hourly timestamps for a whole year
times = collect(ZonedDateTime(DateTime(2019), tz):Hour(1):ZonedDateTime(DateTime(2020), tz))

# This returns a StructVector with solar position data
positions = solar_position(obs, times)

# For plotting, we need to create a DataFrame that includes the timestamps
df = DataFrame(positions)
df.datetime = times

# We can inspect the first few entries
first(df, 5)
tz = TimeZone("Asia/Kolkata")
year = 2019
```

## Simple Sun Path Plot in Cartesian Coordinates

We can visualize solar positions in cartesian coordinates using the `sunpathplot`
function:
We can visualize solar positions in cartesian coordinates using the `analemmas!`
function. The function automatically generates analemmas for all 24 hours of the day:

```@example plotting
fig = Figure(backgroundcolor = (:white, 0.0), textcolor= "#f5ab35")
ax = Axis(fig[1, 1], backgroundcolor = (:white, 0.0))
sunpathplot!(ax, df, hour_labels = false)
analemmas!(ax, obs, year, hour_labels = false)
fig
```

## Polar Coordinates with Hour Labels

We can also work directly with a `DataFrame`. Note that for plotting we need to include
the datetime information, so we add it to the DataFrame.

Plotting in polar coordinates with `sunpathpolarplot` may yield a more intuitive
representation of the solar path. Here, we also enable hourly labels for better
readability:
Plotting in polar coordinates with `analemmas!` may yield a more intuitive
representation of the solar path. Here, we enable hourly labels for better readability:

```@example plotting
fig2 = Figure(backgroundcolor = :transparent, textcolor= "#f5ab35", size = (800, 600))
ax2 = PolarAxis(fig2[1, 1], backgroundcolor = "#1f2424")
sunpathpolarplot!(ax2, df, hour_labels = true)
analemmas!(ax2, obs, year, hour_labels = true)
fig2
```

# Draw individual days
Now let's manually plot the full solar path for specific dates March 21, June 21, and
December 21. Also known as the vernal equinox, summer solstice, and winter solstice,
respectively:

```@example plotting
line_objects = []
for (date, label) in [(Date("2019-03-21"), "Mar 21"),
(Date("2019-06-21"), "Jun 21"),
(Date("2019-12-21"), "Dec 21")]
times = collect(ZonedDateTime(DateTime(date), tz):Minute(5):ZonedDateTime(DateTime(date) + Day(1), tz))
solpos = solar_position(obs, times)
above_horizon = solpos.elevation .> 0
day_df = DataFrame(solpos)
day_df.datetime = times
day_filtered = day_df[above_horizon, :]
day_filtered = solpos[above_horizon]
line_obj = lines!(ax2, deg2rad.(day_filtered.azimuth), day_filtered.zenith,
linewidth = 2, label = label)
push!(line_objects, line_obj)
Expand All @@ -101,11 +96,23 @@ It tells us when the sun rises, reaches its highest point, and sets. And hence a
length of the day. From the figure we can also read that in June the days are longest,
while in December they are shortest.

## Plotting without a custom axis
## Custom Color Schemes

You can customize the color scheme used for the analemmas by passing a `colorscheme` argument.
Here's an example using the `:balance` colorscheme.

Finally, we can also create plots without explicitly defining an axis beforehand.
This is a more concise way to create plots, but it offers less customization:
!!! info
More colorschemes are available in the [Makie documentation](https://docs.makie.org/dev/explanations/colors).

```@example plotting
sunpathpolarplot(df, hour_labels = true, colorbar = true)
fig = Figure(backgroundcolor = (:white, 0.0), textcolor= "#f5ab35")
ax = Axis(fig[1, 1], backgroundcolor = (:white, 0.0))
analemmas!(ax, obs, year, hour_labels = false, colorscheme = :balance)
fig
```

## Docstrings

```@docs
analemmas!
```
36 changes: 14 additions & 22 deletions examples/plotting.jl
Original file line number Diff line number Diff line change
@@ -1,35 +1,27 @@
"""Plot solar positions using SolarPosition.jl with hourly labels."""

using Dates
using TimeZones
using DataFrames
using CairoMakie
using SolarPosition

# define observer location (latitude, longitude, altitude in meters)
tz = tz"Asia/Kolkata"
obs = Observer(28.6, 77.2, 0.0)
obs = Observer(28.6, 77.2, 0.0) # New Delhi, India
year = 2019

# a whole year of hourly timestamps
times = collect(ZonedDateTime(DateTime(2019), tz):Hour(1):ZonedDateTime(DateTime(2020), tz))
positions = solar_position(obs, times)

# plot positions from NamedTuple with hourly labels in polar coordinates
df = DataFrame(positions)
df.datetime = times
sunpathplot(df)

# plot DataFrame with hourly labels in cartesian coordinates
sunpathpolarplot(df, hour_labels = true, colorbar = true)

# plot DataFrame in polar coordinates with hourly labels
# plot in cartesian coordinates with hourly labels
fig = Figure()
ax = PolarAxis(fig[1, 1], title = "Polar Coordinates with Hour Labels")
sunpathpolarplot!(ax, df, hour_labels = true, colorbar = false)
ax = Axis(fig[1, 1], title = "Cartesian Coordinates with Hour Labels")
analemmas!(ax, obs, year, hour_labels = true)
fig

# example without hourly labels for comparison
# plot in polar coordinates with hourly labels
fig2 = Figure()
ax2 = Axis(fig2[1, 1], title = "Cartesian Coordinates (No Labels)")
sunpathplot!(ax2, df; hour_labels = false, colorbar = true)
ax2 = PolarAxis(fig2[1, 1], title = "Polar Coordinates with Hour Labels")
analemmas!(ax2, obs, year, hour_labels = true)
fig2

# example without hourly labels for comparison
fig3 = Figure()
ax3 = Axis(fig3[1, 1], title = "Cartesian Coordinates (No Labels)")
analemmas!(ax3, obs, year, hour_labels = false)
fig3
Loading
Loading