diff --git a/docs/notebooks/144_chart_features.ipynb b/docs/notebooks/144_chart_features.ipynb
new file mode 100644
index 0000000000..b1e9f617e9
--- /dev/null
+++ b/docs/notebooks/144_chart_features.ipynb
@@ -0,0 +1,352 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ "\n",
+ "**Feature and FeatureCollection Charts**\n",
+ "\n",
+ "Uncomment the following line to install [geemap](https://geemap.org) if needed."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# %pip install -U geemap"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Import libraries"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import calendar\n",
+ "import ee\n",
+ "import geemap\n",
+ "from geemap import chart"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "geemap.ee_initialize()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## feature_by_feature\n",
+ "\n",
+ "Features are plotted along the x-axis, labeled by values of a selected property. Series are represented by adjacent columns defined by a list of property names whose values are plotted along the y-axis."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ecoregions = ee.FeatureCollection(\"projects/google/charts_feature_example\")\n",
+ "features = ecoregions.select(\"[0-9][0-9]_tmean|label\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "geemap.ee_to_df(features)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "x_property = \"label\"\n",
+ "y_properties = [str(x).zfill(2) + \"_tmean\" for x in range(1, 13)]\n",
+ "\n",
+ "labels = calendar.month_abbr[1:] # a list of month labels, e.g. ['Jan', 'Feb', ...]\n",
+ "\n",
+ "colors = [\n",
+ " \"#604791\",\n",
+ " \"#1d6b99\",\n",
+ " \"#39a8a7\",\n",
+ " \"#0f8755\",\n",
+ " \"#76b349\",\n",
+ " \"#f0af07\",\n",
+ " \"#e37d05\",\n",
+ " \"#cf513e\",\n",
+ " \"#96356f\",\n",
+ " \"#724173\",\n",
+ " \"#9c4f97\",\n",
+ " \"#696969\",\n",
+ "]\n",
+ "title = \"Average Monthly Temperature by Ecoregion\"\n",
+ "x_label = \"Ecoregion\"\n",
+ "y_label = \"Temperature\""
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig = chart.feature_by_feature(\n",
+ " features,\n",
+ " x_property,\n",
+ " y_properties,\n",
+ " colors=colors,\n",
+ " labels=labels,\n",
+ " title=title,\n",
+ " x_label=x_label,\n",
+ " y_label=y_label,\n",
+ ")\n",
+ "fig"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "![](https://i.imgur.com/MZa99Vf.png)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## feature.by_property"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ecoregions = ee.FeatureCollection(\"projects/google/charts_feature_example\")\n",
+ "features = ecoregions.select(\"[0-9][0-9]_ppt|label\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "geemap.ee_to_df(features)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "keys = [str(x).zfill(2) + \"_ppt\" for x in range(1, 13)]\n",
+ "values = calendar.month_abbr[1:] # a list of month labels, e.g. ['Jan', 'Feb', ...]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "x_properties = dict(zip(keys, values))\n",
+ "series_property = \"label\"\n",
+ "title = \"Average Ecoregion Precipitation by Month\"\n",
+ "colors = [\"#f0af07\", \"#0f8755\", \"#76b349\"]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig = chart.feature_by_property(\n",
+ " features,\n",
+ " x_properties,\n",
+ " series_property,\n",
+ " title=title,\n",
+ " colors=colors,\n",
+ " x_label=\"Month\",\n",
+ " y_label=\"Precipitation (mm)\",\n",
+ " legend_location=\"top-left\",\n",
+ ")\n",
+ "fig"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "![](https://i.imgur.com/6RhuUc7.png)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## feature_groups"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ecoregions = ee.FeatureCollection(\"projects/google/charts_feature_example\")\n",
+ "features = ecoregions.select(\"[0-9][0-9]_ppt|label\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "features = ee.FeatureCollection(\"projects/google/charts_feature_example\")\n",
+ "x_property = \"label\"\n",
+ "y_property = \"01_tmean\"\n",
+ "series_property = \"warm\"\n",
+ "title = \"Average January Temperature by Ecoregion\"\n",
+ "colors = [\"#cf513e\", \"#1d6b99\"]\n",
+ "labels = [\"Warm\", \"Cold\"]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "chart.feature_groups(\n",
+ " features,\n",
+ " x_property,\n",
+ " y_property,\n",
+ " series_property,\n",
+ " title=title,\n",
+ " colors=colors,\n",
+ " x_label=\"Ecoregion\",\n",
+ " y_label=\"January Temperature (°C)\",\n",
+ " legend_location=\"top-right\",\n",
+ " labels=labels,\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "![](https://i.imgur.com/YFZlJtc.png)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## feature_histogram"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "source = ee.ImageCollection(\"OREGONSTATE/PRISM/Norm91m\").toBands()\n",
+ "region = ee.Geometry.Rectangle(-123.41, 40.43, -116.38, 45.14)\n",
+ "features = source.sample(region, 5000)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "geemap.ee_to_df(features.limit(5).select([\"07_ppt\"]))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "property = \"07_ppt\"\n",
+ "title = \"July Precipitation Distribution for NW USA\""
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig = chart.feature_histogram(\n",
+ " features,\n",
+ " property,\n",
+ " max_buckets=None,\n",
+ " title=title,\n",
+ " x_label=\"Precipitation (mm)\",\n",
+ " y_label=\"Pixel Count\",\n",
+ " colors=[\"#1d6b99\"],\n",
+ ")\n",
+ "fig"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "![](https://i.imgur.com/ErIp7Oy.png)"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "geo",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.11.8"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/docs/notebooks/145_chart_image.ipynb b/docs/notebooks/145_chart_image.ipynb
new file mode 100644
index 0000000000..09fa2b15eb
--- /dev/null
+++ b/docs/notebooks/145_chart_image.ipynb
@@ -0,0 +1,304 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ "\n",
+ "**Image Charts**\n",
+ "\n",
+ "Uncomment the following line to install [geemap](https://geemap.org) if needed."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# %pip install -U geemap"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Import libraries"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import calendar\n",
+ "import ee\n",
+ "import geemap\n",
+ "from geemap import chart"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "geemap.ee_initialize()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## image_by_region"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ecoregions = ee.FeatureCollection(\"projects/google/charts_feature_example\")\n",
+ "image = (\n",
+ " ee.ImageCollection(\"OREGONSTATE/PRISM/Norm91m\").toBands().select(\"[0-9][0-9]_tmean\")\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "labels = calendar.month_abbr[1:] # a list of month labels, e.g. ['Jan', 'Feb', ...]\n",
+ "title = \"Average Monthly Temperature by Ecoregion\""
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig = chart.image_by_region(\n",
+ " image,\n",
+ " ecoregions,\n",
+ " reducer=\"mean\",\n",
+ " scale=500,\n",
+ " x_property=\"label\",\n",
+ " title=title,\n",
+ " x_label=\"Ecoregion\",\n",
+ " y_label=\"Temperature\",\n",
+ " labels=labels,\n",
+ ")\n",
+ "fig"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "![](https://i.imgur.com/y4rp3dK.png)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## image_regions"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ecoregions = ee.FeatureCollection(\"projects/google/charts_feature_example\")\n",
+ "image = (\n",
+ " ee.ImageCollection(\"OREGONSTATE/PRISM/Norm91m\").toBands().select(\"[0-9][0-9]_ppt\")\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "keys = [str(x).zfill(2) + \"_ppt\" for x in range(1, 13)]\n",
+ "values = calendar.month_abbr[1:] # a list of month labels, e.g. ['Jan', 'Feb', ...]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "x_properties = dict(zip(keys, values))\n",
+ "title = \"Average Ecoregion Precipitation by Month\"\n",
+ "colors = [\"#f0af07\", \"#0f8755\", \"#76b349\"]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig = chart.image_regions(\n",
+ " image,\n",
+ " ecoregions,\n",
+ " reducer=\"mean\",\n",
+ " scale=500,\n",
+ " series_property=\"label\",\n",
+ " x_labels=x_properties,\n",
+ " title=title,\n",
+ " colors=colors,\n",
+ " x_label=\"Month\",\n",
+ " y_label=\"Precipitation (mm)\",\n",
+ " legend_location=\"top-left\",\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "![](https://i.imgur.com/5WJVCNY.png)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## image_by_class"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ecoregions = ee.FeatureCollection(\"projects/google/charts_feature_example\")\n",
+ "\n",
+ "image = (\n",
+ " ee.ImageCollection(\"MODIS/061/MOD09A1\")\n",
+ " .filter(ee.Filter.date(\"2018-06-01\", \"2018-09-01\"))\n",
+ " .select(\"sur_refl_b0[0-7]\")\n",
+ " .mean()\n",
+ " .select([2, 3, 0, 1, 4, 5, 6])\n",
+ ")\n",
+ "\n",
+ "wavelengths = [469, 555, 655, 858, 1240, 1640, 2130]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig = chart.image_by_class(\n",
+ " image,\n",
+ " class_band=\"label\",\n",
+ " region=ecoregions,\n",
+ " reducer=\"MEAN\",\n",
+ " scale=500,\n",
+ " x_labels=wavelengths,\n",
+ " title=\"Ecoregion Spectral Signatures\",\n",
+ " x_label=\"Wavelength (nm)\",\n",
+ " y_label=\"Reflectance (x1e4)\",\n",
+ " colors=[\"#f0af07\", \"#0f8755\", \"#76b349\"],\n",
+ " legend_location=\"top-left\",\n",
+ " interpolation=\"basis\",\n",
+ ")\n",
+ "fig"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "![](https://i.imgur.com/XqYHvBV.png)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## image_histogram"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "image = (\n",
+ " ee.ImageCollection(\"MODIS/061/MOD09A1\")\n",
+ " .filter(ee.Filter.date(\"2018-06-01\", \"2018-09-01\"))\n",
+ " .select([\"sur_refl_b01\", \"sur_refl_b02\", \"sur_refl_b06\"])\n",
+ " .mean()\n",
+ ")\n",
+ "\n",
+ "region = ee.Geometry.Rectangle([-112.60, 40.60, -111.18, 41.22])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig = chart.image_histogram(\n",
+ " image,\n",
+ " region,\n",
+ " scale=500,\n",
+ " max_buckets=200,\n",
+ " min_bucket_width=1.0,\n",
+ " max_raw=1000,\n",
+ " max_pixels=int(1e6),\n",
+ " title=\"MODIS SR Reflectance Histogram\",\n",
+ " labels=[\"Red\", \"NIR\", \"SWIR\"],\n",
+ " colors=[\"#cf513e\", \"#1d6b99\", \"#f0af07\"],\n",
+ ")\n",
+ "fig"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "![](https://i.imgur.com/mY4yoYH.png)"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "geo",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.11.8"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/docs/notebooks/146_chart_image_collection.ipynb b/docs/notebooks/146_chart_image_collection.ipynb
new file mode 100644
index 0000000000..0161bd2461
--- /dev/null
+++ b/docs/notebooks/146_chart_image_collection.ipynb
@@ -0,0 +1,419 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ "\n",
+ "**ImageCollection Charts**\n",
+ "\n",
+ "Uncomment the following line to install [geemap](https://geemap.org) if needed."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# %pip install -U geemap"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Import libraries"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import ee\n",
+ "import geemap\n",
+ "from geemap import chart"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "geemap.ee_initialize()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## image_series"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Define the forest feature collection.\n",
+ "forest = ee.FeatureCollection(\"projects/google/charts_feature_example\").filter(\n",
+ " ee.Filter.eq(\"label\", \"Forest\")\n",
+ ")\n",
+ "\n",
+ "# Load MODIS vegetation indices data and subset a decade of images.\n",
+ "veg_indices = (\n",
+ " ee.ImageCollection(\"MODIS/061/MOD13A1\")\n",
+ " .filter(ee.Filter.date(\"2010-01-01\", \"2020-01-01\"))\n",
+ " .select([\"NDVI\", \"EVI\"])\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "title = \"Average Vegetation Index Value by Date for Forest\"\n",
+ "x_label = \"Year\"\n",
+ "y_label = \"Vegetation index (x1e4)\"\n",
+ "colors = [\"#e37d05\", \"#1d6b99\"]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig = chart.image_series(\n",
+ " veg_indices,\n",
+ " region=forest,\n",
+ " reducer=ee.Reducer.mean(),\n",
+ " scale=500,\n",
+ " x_property=\"system:time_start\",\n",
+ " chart_type=\"LineChart\",\n",
+ " x_cols=\"date\",\n",
+ " y_cols=[\"NDVI\", \"EVI\"],\n",
+ " colors=colors,\n",
+ " title=title,\n",
+ " x_label=x_label,\n",
+ " y_label=y_label,\n",
+ " legend_location=\"right\",\n",
+ ")\n",
+ "fig"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "![](https://i.imgur.com/r9zSJh6.png)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## image_series_by_region"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Import the example feature collection.\n",
+ "ecoregions = ee.FeatureCollection(\"projects/google/charts_feature_example\")\n",
+ "\n",
+ "# Load MODIS vegetation indices data and subset a decade of images.\n",
+ "veg_indices = (\n",
+ " ee.ImageCollection(\"MODIS/061/MOD13A1\")\n",
+ " .filter(ee.Filter.date(\"2010-01-01\", \"2020-01-01\"))\n",
+ " .select([\"NDVI\"])\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "title = \"Average NDVI Value by Date\"\n",
+ "x_label = \"Date\"\n",
+ "y_label = \"NDVI (x1e4)\"\n",
+ "x_cols = \"index\"\n",
+ "y_cols = [\"Desert\", \"Forest\", \"Grassland\"]\n",
+ "colors = [\"#f0af07\", \"#0f8755\", \"#76b349\"]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig = chart.image_series_by_region(\n",
+ " veg_indices,\n",
+ " regions=ecoregions,\n",
+ " reducer=ee.Reducer.mean(),\n",
+ " band=\"NDVI\",\n",
+ " scale=500,\n",
+ " x_property=\"system:time_start\",\n",
+ " series_property=\"label\",\n",
+ " chart_type=\"LineChart\",\n",
+ " x_cols=x_cols,\n",
+ " y_cols=y_cols,\n",
+ " title=title,\n",
+ " x_label=x_label,\n",
+ " y_label=y_label,\n",
+ " colors=colors,\n",
+ " stroke_width=3,\n",
+ " legend_location=\"bottom-left\",\n",
+ ")\n",
+ "fig"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "![](https://i.imgur.com/rnILSfI.png)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## image_doy_series"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Import the example feature collection and subset the grassland feature.\n",
+ "grassland = ee.FeatureCollection(\"projects/google/charts_feature_example\").filter(\n",
+ " ee.Filter.eq(\"label\", \"Grassland\")\n",
+ ")\n",
+ "\n",
+ "# Load MODIS vegetation indices data and subset a decade of images.\n",
+ "veg_indices = (\n",
+ " ee.ImageCollection(\"MODIS/061/MOD13A1\")\n",
+ " .filter(ee.Filter.date(\"2010-01-01\", \"2020-01-01\"))\n",
+ " .select([\"NDVI\", \"EVI\"])\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "title = \"Average Vegetation Index Value by Day of Year for Grassland\"\n",
+ "x_label = \"Day of Year\"\n",
+ "y_label = \"Vegetation Index (x1e4)\"\n",
+ "colors = [\"#f0af07\", \"#0f8755\"]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig = chart.image_doy_series(\n",
+ " image_collection=veg_indices,\n",
+ " region=grassland,\n",
+ " scale=500,\n",
+ " chart_type=\"LineChart\",\n",
+ " title=title,\n",
+ " x_label=x_label,\n",
+ " y_label=y_label,\n",
+ " colors=colors,\n",
+ " stroke_width=5,\n",
+ ")\n",
+ "fig"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "![](https://i.imgur.com/F0z088e.png)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## image_doy_series_by_year"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Import the example feature collection and subset the grassland feature.\n",
+ "grassland = ee.FeatureCollection(\"projects/google/charts_feature_example\").filter(\n",
+ " ee.Filter.eq(\"label\", \"Grassland\")\n",
+ ")\n",
+ "\n",
+ "# Load MODIS vegetation indices data and subset years 2012 and 2019.\n",
+ "veg_indices = (\n",
+ " ee.ImageCollection(\"MODIS/061/MOD13A1\")\n",
+ " .filter(\n",
+ " ee.Filter.Or(\n",
+ " ee.Filter.date(\"2012-01-01\", \"2013-01-01\"),\n",
+ " ee.Filter.date(\"2019-01-01\", \"2020-01-01\"),\n",
+ " )\n",
+ " )\n",
+ " .select([\"NDVI\", \"EVI\"])\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "title = \"Average Vegetation Index Value by Day of Year for Grassland\"\n",
+ "x_label = \"Day of Year\"\n",
+ "y_label = \"Vegetation Index (x1e4)\"\n",
+ "colors = [\"#e37d05\", \"#1d6b99\"]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig = chart.doy_series_by_year(\n",
+ " veg_indices,\n",
+ " band_name=\"NDVI\",\n",
+ " region=grassland,\n",
+ " scale=500,\n",
+ " chart_type=\"LineChart\",\n",
+ " colors=colors,\n",
+ " title=title,\n",
+ " x_label=x_label,\n",
+ " y_label=y_label,\n",
+ " stroke_width=5,\n",
+ ")\n",
+ "fig"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "![](https://i.imgur.com/ui6zpbl.png)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## image_doy_series_by_region"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Import the example feature collection and subset the grassland feature.\n",
+ "ecoregions = ee.FeatureCollection(\"projects/google/charts_feature_example\")\n",
+ "\n",
+ "# Load MODIS vegetation indices data and subset a decade of images.\n",
+ "veg_indices = (\n",
+ " ee.ImageCollection(\"MODIS/061/MOD13A1\")\n",
+ " .filter(ee.Filter.date(\"2010-01-01\", \"2020-01-01\"))\n",
+ " .select([\"NDVI\"])\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "title = \"Average Vegetation Index Value by Day of Year for Grassland\"\n",
+ "x_label = \"Day of Year\"\n",
+ "y_label = \"Vegetation Index (x1e4)\"\n",
+ "colors = [\"#f0af07\", \"#0f8755\", \"#76b349\"]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig = chart.image_doy_series_by_region(\n",
+ " veg_indices,\n",
+ " \"NDVI\",\n",
+ " ecoregions,\n",
+ " region_reducer=\"mean\",\n",
+ " scale=500,\n",
+ " year_reducer=ee.Reducer.mean(),\n",
+ " start_day=1,\n",
+ " end_day=366,\n",
+ " series_property=\"label\",\n",
+ " stroke_width=5,\n",
+ " chart_type=\"LineChart\",\n",
+ " title=title,\n",
+ " x_label=x_label,\n",
+ " y_label=y_label,\n",
+ " colors=colors,\n",
+ " legend_location=\"right\",\n",
+ ")\n",
+ "fig"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "![](https://i.imgur.com/eGqGoRs.png)"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "geo",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.11.8"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/docs/notebooks/147_chart_array_list.ipynb b/docs/notebooks/147_chart_array_list.ipynb
new file mode 100644
index 0000000000..5b9024c0ea
--- /dev/null
+++ b/docs/notebooks/147_chart_array_list.ipynb
@@ -0,0 +1,377 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ "\n",
+ "**Array and List Charts**\n",
+ "\n",
+ "Uncomment the following line to install [geemap](https://geemap.org) if needed."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# %pip install -U geemap"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Import libraries"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import ee\n",
+ "import geemap\n",
+ "from geemap import chart"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "geemap.ee_initialize()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Scatter plot"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Import the example feature collection and subset the forest feature.\n",
+ "forest = ee.FeatureCollection(\"projects/google/charts_feature_example\").filter(\n",
+ " ee.Filter.eq(\"label\", \"Forest\")\n",
+ ")\n",
+ "\n",
+ "# Define a MODIS surface reflectance composite.\n",
+ "modisSr = (\n",
+ " ee.ImageCollection(\"MODIS/061/MOD09A1\")\n",
+ " .filter(ee.Filter.date(\"2018-06-01\", \"2018-09-01\"))\n",
+ " .select(\"sur_refl_b0[0-7]\")\n",
+ " .mean()\n",
+ ")\n",
+ "\n",
+ "# Reduce MODIS reflectance bands by forest region; get a dictionary with\n",
+ "# band names as keys, pixel values as lists.\n",
+ "pixel_vals = modisSr.reduceRegion(\n",
+ " **{\"reducer\": ee.Reducer.toList(), \"geometry\": forest.geometry(), \"scale\": 2000}\n",
+ ")\n",
+ "\n",
+ "# Convert NIR and SWIR value lists to an array to be plotted along the y-axis.\n",
+ "y_values = pixel_vals.toArray([\"sur_refl_b02\", \"sur_refl_b06\"])\n",
+ "\n",
+ "\n",
+ "# Get the red band value list; to be plotted along the x-axis.\n",
+ "x_values = ee.List(pixel_vals.get(\"sur_refl_b01\"))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "title = \"Relationship Among Spectral Bands for Forest Pixels\"\n",
+ "colors = [\"rgba(29,107,153,0.4)\", \"rgba(207,81,62,0.4)\"]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig = chart.array_values(\n",
+ " y_values,\n",
+ " axis=1,\n",
+ " x_labels=x_values,\n",
+ " series_names=[\"NIR\", \"SWIR\"],\n",
+ " chart_type=\"ScatterChart\",\n",
+ " colors=colors,\n",
+ " title=title,\n",
+ " x_label=\"Red reflectance (x1e4)\",\n",
+ " y_label=\"NIR & SWIR reflectance (x1e4)\",\n",
+ " default_size=15,\n",
+ " xlim=(0, 800),\n",
+ ")\n",
+ "fig"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "![](https://i.imgur.com/zkPlZIO.png)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "x = ee.List(pixel_vals.get(\"sur_refl_b01\"))\n",
+ "y = ee.List(pixel_vals.get(\"sur_refl_b06\"))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig = chart.array_values(\n",
+ " y,\n",
+ " x_labels=x,\n",
+ " series_names=[\"SWIR\"],\n",
+ " chart_type=\"ScatterChart\",\n",
+ " colors=[\"rgba(207,81,62,0.4)\"],\n",
+ " title=title,\n",
+ " x_label=\"Red reflectance (x1e4)\",\n",
+ " y_label=\"SWIR reflectance (x1e4)\",\n",
+ " default_size=15,\n",
+ " xlim=(0, 800),\n",
+ ")\n",
+ "fig"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "![](https://i.imgur.com/WHUHjH6.png)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ " ## Transect line plot"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Define a line across the Olympic Peninsula, USA.\n",
+ "transect = ee.Geometry.LineString([[-122.8, 47.8], [-124.5, 47.8]])\n",
+ "\n",
+ "# Define a pixel coordinate image.\n",
+ "lat_lon_img = ee.Image.pixelLonLat()\n",
+ "\n",
+ "# Import a digital surface model and add latitude and longitude bands.\n",
+ "elev_img = ee.Image(\"USGS/SRTMGL1_003\").select(\"elevation\").addBands(lat_lon_img)\n",
+ "\n",
+ "# Reduce elevation and coordinate bands by transect line; get a dictionary with\n",
+ "# band names as keys, pixel values as lists.\n",
+ "elev_transect = elev_img.reduceRegion(\n",
+ " reducer=ee.Reducer.toList(),\n",
+ " geometry=transect,\n",
+ " scale=1000,\n",
+ ")\n",
+ "\n",
+ "# Get longitude and elevation value lists from the reduction dictionary.\n",
+ "lon = ee.List(elev_transect.get(\"longitude\"))\n",
+ "elev = ee.List(elev_transect.get(\"elevation\"))\n",
+ "\n",
+ "# Sort the longitude and elevation values by ascending longitude.\n",
+ "lon_sort = lon.sort(lon)\n",
+ "elev_sort = elev.sort(lon)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig = chart.array_values(\n",
+ " elev_sort,\n",
+ " x_labels=lon_sort,\n",
+ " series_names=[\"Elevation\"],\n",
+ " chart_type=\"AreaChart\",\n",
+ " colors=[\"#1d6b99\"],\n",
+ " title=\"Elevation Profile Across Longitude\",\n",
+ " x_label=\"Longitude\",\n",
+ " y_label=\"Elevation (m)\",\n",
+ " stroke_width=5,\n",
+ " fill=\"bottom\",\n",
+ " fill_opacities=[0.4],\n",
+ " ylim=(0, 2500),\n",
+ ")\n",
+ "fig"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "![](https://i.imgur.com/k3XRita.png)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Metadata scatter plot"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Import a Landsat 8 collection and filter to a single path/row.\n",
+ "col = ee.ImageCollection(\"LANDSAT/LC08/C02/T1_L2\").filter(\n",
+ " ee.Filter.expression(\"WRS_PATH == 45 && WRS_ROW == 30\")\n",
+ ")\n",
+ "\n",
+ "# Reduce image properties to a series of lists; one for each selected property.\n",
+ "propVals = col.reduceColumns(\n",
+ " reducer=ee.Reducer.toList().repeat(2),\n",
+ " selectors=[\"CLOUD_COVER\", \"GEOMETRIC_RMSE_MODEL\"],\n",
+ ").get(\"list\")\n",
+ "\n",
+ "# Get selected image property value lists; to be plotted along x and y axes.\n",
+ "x = ee.List(ee.List(propVals).get(0))\n",
+ "y = ee.List(ee.List(propVals).get(1))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "colors = [geemap.hex_to_rgba(\"#96356f\", 0.4)]\n",
+ "print(colors)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig = chart.array_values(\n",
+ " y,\n",
+ " x_labels=x,\n",
+ " series_names=[\"RMSE\"],\n",
+ " chart_type=\"ScatterChart\",\n",
+ " colors=colors,\n",
+ " title=\"Landsat 8 Image Collection Metadata (045030)\",\n",
+ " x_label=\"Cloud cover (%)\",\n",
+ " y_label=\"Geometric RMSE (m)\",\n",
+ " default_size=15,\n",
+ ")\n",
+ "fig"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "![](https://i.imgur.com/3COY3xd.png)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Mapped function scatter & line plot"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import math\n",
+ "\n",
+ "start = -2 * math.pi\n",
+ "end = 2 * math.pi\n",
+ "points = ee.List.sequence(start, end, None, 50)\n",
+ "\n",
+ "\n",
+ "def sin_func(val):\n",
+ " return ee.Number(val).sin()\n",
+ "\n",
+ "\n",
+ "values = points.map(sin_func)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig = chart.array_values(\n",
+ " values,\n",
+ " points,\n",
+ " chart_type=\"LineChart\",\n",
+ " colors=[\"#39a8a7\"],\n",
+ " title=\"Sine Function\",\n",
+ " x_label=\"radians\",\n",
+ " y_label=\"sin(x)\",\n",
+ " marker=\"circle\",\n",
+ ")\n",
+ "fig"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "![](https://i.imgur.com/7qcxvey.png)"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "geo",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.11.8"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/docs/notebooks/148_chart_data_table.ipynb b/docs/notebooks/148_chart_data_table.ipynb
new file mode 100644
index 0000000000..2f2a34dc88
--- /dev/null
+++ b/docs/notebooks/148_chart_data_table.ipynb
@@ -0,0 +1,363 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ "\n",
+ "**DataTable Charts**\n",
+ "\n",
+ "Uncomment the following line to install [geemap](https://geemap.org) if needed."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# %pip install -U geemap"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Import libraries"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import ee\n",
+ "import geemap\n",
+ "from geemap import chart\n",
+ "import pandas as pd"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "geemap.ee_initialize()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Manual DataTable chart"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "data = {\n",
+ " \"State\": [\"CA\", \"NY\", \"IL\", \"MI\", \"OR\"],\n",
+ " \"Population\": [37253956, 19378102, 12830632, 9883640, 3831074],\n",
+ "}\n",
+ "\n",
+ "df = pd.DataFrame(data)\n",
+ "df"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig = chart.Chart(\n",
+ " df,\n",
+ " x_cols=[\"State\"],\n",
+ " y_cols=[\"Population\"],\n",
+ " chart_type=\"ColumnChart\",\n",
+ " colors=[\"#1d6b99\"],\n",
+ " title=\"State Population (US census, 2010)\",\n",
+ " x_label=\"State\",\n",
+ " y_label=\"Population\",\n",
+ ")\n",
+ "fig"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "![](https://i.imgur.com/vuxNmuh.png)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Computed DataTable chart"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Import the example feature collection and subset the forest feature.\n",
+ "forest = ee.FeatureCollection(\"projects/google/charts_feature_example\").filter(\n",
+ " ee.Filter.eq(\"label\", \"Forest\")\n",
+ ")\n",
+ "\n",
+ "# Load MODIS vegetation indices data and subset a decade of images.\n",
+ "veg_indices = (\n",
+ " ee.ImageCollection(\"MODIS/061/MOD13A1\")\n",
+ " .filter(ee.Filter.date(\"2010-01-01\", \"2020-01-01\"))\n",
+ " .select([\"NDVI\", \"EVI\"])\n",
+ ")\n",
+ "\n",
+ "# Build a feature collection where each feature has a property that represents\n",
+ "# a DataFrame row.\n",
+ "\n",
+ "\n",
+ "def aggregate(img):\n",
+ " # Reduce the image to the mean of pixels intersecting the forest ecoregion.\n",
+ " stat = img.reduceRegion(\n",
+ " **{\"reducer\": ee.Reducer.mean(), \"geometry\": forest, \"scale\": 500}\n",
+ " )\n",
+ "\n",
+ " # Extract the reduction results along with the image date.\n",
+ " date = geemap.image_date(img)\n",
+ " evi = stat.get(\"EVI\")\n",
+ " ndvi = stat.get(\"NDVI\")\n",
+ "\n",
+ " # Make a list of observation attributes to define a row in the DataTable.\n",
+ " row = ee.List([date, evi, ndvi])\n",
+ "\n",
+ " # Return the row as a property of an ee.Feature.\n",
+ " return ee.Feature(None, {\"row\": row})\n",
+ "\n",
+ "\n",
+ "reduction_table = veg_indices.map(aggregate)\n",
+ "\n",
+ "# Aggregate the 'row' property from all features in the new feature collection\n",
+ "# to make a server-side 2-D list (DataTable).\n",
+ "data_table_server = reduction_table.aggregate_array(\"row\")\n",
+ "\n",
+ "# Define column names and properties for the DataTable. The order should\n",
+ "# correspond to the order in the construction of the 'row' property above.\n",
+ "column_header = ee.List([[\"Date\", \"EVI\", \"NDVI\"]])\n",
+ "\n",
+ "# Concatenate the column header to the table.\n",
+ "data_table_server = column_header.cat(data_table_server)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "data_table = chart.DataTable(data_table_server, date_column=\"Date\")\n",
+ "data_table.head()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig = chart.Chart(\n",
+ " data_table,\n",
+ " chart_type=\"LineChart\",\n",
+ " x_cols=\"Date\",\n",
+ " y_cols=[\"EVI\", \"NDVI\"],\n",
+ " colors=[\"#e37d05\", \"#1d6b99\"],\n",
+ " title=\"Average Vegetation Index Value by Date for Forest\",\n",
+ " x_label=\"Date\",\n",
+ " y_label=\"Vegetation index (x1e4)\",\n",
+ " stroke_width=3,\n",
+ " legend_location=\"right\",\n",
+ ")\n",
+ "fig"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "![](https://i.imgur.com/PWei7QC.png)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Interval chart"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Define a point to extract an NDVI time series for.\n",
+ "geometry = ee.Geometry.Point([-121.679, 36.479])\n",
+ "\n",
+ "# Define a band of interest (NDVI), import the MODIS vegetation index dataset,\n",
+ "# and select the band.\n",
+ "band = \"NDVI\"\n",
+ "ndvi_col = ee.ImageCollection(\"MODIS/061/MOD13Q1\").select(band)\n",
+ "\n",
+ "# Map over the collection to add a day of year (doy) property to each image.\n",
+ "\n",
+ "\n",
+ "def set_doy(img):\n",
+ " doy = ee.Date(img.get(\"system:time_start\")).getRelative(\"day\", \"year\")\n",
+ " # Add 8 to day of year number so that the doy label represents the middle of\n",
+ " # the 16-day MODIS NDVI composite.\n",
+ " return img.set(\"doy\", ee.Number(doy).add(8))\n",
+ "\n",
+ "\n",
+ "ndvi_col = ndvi_col.map(set_doy)\n",
+ "\n",
+ "# Join all coincident day of year observations into a set of image collections.\n",
+ "distinct_doy = ndvi_col.filterDate(\"2013-01-01\", \"2014-01-01\")\n",
+ "filter = ee.Filter.equals(**{\"leftField\": \"doy\", \"rightField\": \"doy\"})\n",
+ "join = ee.Join.saveAll(\"doy_matches\")\n",
+ "join_col = ee.ImageCollection(join.apply(distinct_doy, ndvi_col, filter))\n",
+ "\n",
+ "# Calculate the absolute range, interquartile range, and median for the set\n",
+ "# of images composing each coincident doy observation group. The result is\n",
+ "# an image collection with an image representative per unique doy observation\n",
+ "# with bands that describe the 0, 25, 50, 75, 100 percentiles for the set of\n",
+ "# coincident doy images.\n",
+ "\n",
+ "\n",
+ "def cal_percentiles(img):\n",
+ " doyCol = ee.ImageCollection.fromImages(img.get(\"doy_matches\"))\n",
+ "\n",
+ " return doyCol.reduce(\n",
+ " ee.Reducer.percentile([0, 25, 50, 75, 100], [\"p0\", \"p25\", \"p50\", \"p75\", \"p100\"])\n",
+ " ).set({\"doy\": img.get(\"doy\")})\n",
+ "\n",
+ "\n",
+ "comp = ee.ImageCollection(join_col.map(cal_percentiles))\n",
+ "\n",
+ "# Extract the inter-annual NDVI doy percentile statistics for the\n",
+ "# point of interest per unique doy representative. The result is\n",
+ "# is a feature collection where each feature is a doy representative that\n",
+ "# contains a property (row) describing the respective inter-annual NDVI\n",
+ "# variance, formatted as a list of values.\n",
+ "\n",
+ "\n",
+ "def order_percentiles(img):\n",
+ " stats = ee.Dictionary(\n",
+ " img.reduceRegion(\n",
+ " **{\"reducer\": ee.Reducer.first(), \"geometry\": geometry, \"scale\": 250}\n",
+ " )\n",
+ " )\n",
+ "\n",
+ " # Order the percentile reduction elements according to how you want columns\n",
+ " # in the DataTable arranged (x-axis values need to be first).\n",
+ " row = ee.List(\n",
+ " [\n",
+ " img.get(\"doy\"),\n",
+ " stats.get(band + \"_p50\"),\n",
+ " stats.get(band + \"_p0\"),\n",
+ " stats.get(band + \"_p25\"),\n",
+ " stats.get(band + \"_p75\"),\n",
+ " stats.get(band + \"_p100\"),\n",
+ " ]\n",
+ " )\n",
+ "\n",
+ " # Return the row as a property of an ee.Feature.\n",
+ " return ee.Feature(None, {\"row\": row})\n",
+ "\n",
+ "\n",
+ "reduction_table = comp.map(order_percentiles)\n",
+ "\n",
+ "# Aggregate the 'row' properties to make a server-side 2-D array (DataTable).\n",
+ "data_table_server = reduction_table.aggregate_array(\"row\")\n",
+ "\n",
+ "# Define column names and properties for the DataTable. The order should\n",
+ "# correspond to the order in the construction of the 'row' property above.\n",
+ "column_header = ee.List([[\"DOY\", \"median\", \"p0\", \"p25\", \"p75\", \"p100\"]])\n",
+ "\n",
+ "# Concatenate the column header to the table.\n",
+ "data_table_server = column_header.cat(data_table_server)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "df = chart.DataTable(data_table_server)\n",
+ "df.head()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig = chart.Chart(\n",
+ " df,\n",
+ " chart_type=\"IntervalChart\",\n",
+ " x_cols=\"DOY\",\n",
+ " y_cols=[\"p0\", \"p25\", \"median\", \"p75\", \"p100\"],\n",
+ " title=\"Annual NDVI Time Series with Inter-Annual Variance\",\n",
+ " x_label=\"Day of Year\",\n",
+ " y_label=\"Vegetation index (x1e4)\",\n",
+ " stroke_width=1,\n",
+ " fill=\"between\",\n",
+ " fill_colors=[\"#b6d1c6\", \"#83b191\", \"#83b191\", \"#b6d1c6\"],\n",
+ " fill_opacities=[0.6] * 4,\n",
+ " labels=[\"p0\", \"p25\", \"median\", \"p75\", \"p100\"],\n",
+ " display_legend=True,\n",
+ " legend_location=\"top-right\",\n",
+ " ylim=(0, 10000),\n",
+ ")\n",
+ "fig"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "![](https://i.imgur.com/i8ZrGPR.png)"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "geo",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.11.8"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/docs/notebooks/63_charts.ipynb b/docs/notebooks/63_charts.ipynb
index bb392ee003..e532da30df 100644
--- a/docs/notebooks/63_charts.ipynb
+++ b/docs/notebooks/63_charts.ipynb
@@ -148,7 +148,7 @@
"metadata": {},
"outputs": [],
"source": [
- "chart.feature_byFeature(features, xProperty, yProperties, **options)"
+ "chart.feature_by_feature(features, xProperty, yProperties, **options)"
]
},
{
@@ -311,7 +311,7 @@
"metadata": {},
"outputs": [],
"source": [
- "chart.feature_byProperty(features, xProperties, seriesProperty, **options)"
+ "chart.feature_by_property(features, xProperties, seriesProperty, **options)"
]
},
{
diff --git a/examples/notebooks/63_charts.ipynb b/examples/notebooks/63_charts.ipynb
index bb392ee003..e532da30df 100644
--- a/examples/notebooks/63_charts.ipynb
+++ b/examples/notebooks/63_charts.ipynb
@@ -148,7 +148,7 @@
"metadata": {},
"outputs": [],
"source": [
- "chart.feature_byFeature(features, xProperty, yProperties, **options)"
+ "chart.feature_by_feature(features, xProperty, yProperties, **options)"
]
},
{
@@ -311,7 +311,7 @@
"metadata": {},
"outputs": [],
"source": [
- "chart.feature_byProperty(features, xProperties, seriesProperty, **options)"
+ "chart.feature_by_property(features, xProperties, seriesProperty, **options)"
]
},
{
diff --git a/geemap/chart.py b/geemap/chart.py
index 1d2e8073c3..df005e82e3 100644
--- a/geemap/chart.py
+++ b/geemap/chart.py
@@ -1,4 +1,4 @@
-"""Module for creating charts for Earth Engine data.
+"""Module for creating charts from Earth Engine data.
"""
# *******************************************************************************#
@@ -9,18 +9,518 @@
import ee
import pandas as pd
import numpy as np
+import bqplot as bq
+import ipywidgets as widgets
from bqplot import Tooltip
from bqplot import pyplot as plt
+from IPython.display import display
+from .common import ee_to_df, zonal_stats, image_dates, hex_to_rgba
-from .common import ee_to_df, zonal_stats
+from typing import List, Optional, Union, Dict, Any, Tuple
-from typing import Union
+
+class DataTable(pd.DataFrame):
+
+ def __init__(
+ self,
+ data: Union[Dict[str, List[Any]], pd.DataFrame, None] = None,
+ date_column: Optional[str] = None,
+ date_format: Optional[str] = None,
+ **kwargs: Any,
+ ) -> None:
+ """
+ Initializes the DataTable with data.
+
+ Args:
+ data (Union[Dict[str, List[Any]], pd.DataFrame, None]): The input
+ data. If it's a dictionary, it will be converted to a DataFrame.
+ date_column (Optional[str]): The date column to convert to a DataFrame.
+ date_format (Optional[str]): The format of the date column.
+ **kwargs: Additional keyword arguments to pass to the pd.DataFrame
+ constructor.
+ """
+ if isinstance(data, ee.FeatureCollection):
+ data = ee_to_df(data)
+ elif isinstance(data, ee.List):
+ data = data.getInfo()
+ kwargs["columns"] = data[0]
+ data = data[1:]
+
+ super().__init__(data, **kwargs)
+
+ if date_column is not None:
+ self[date_column] = pd.to_datetime(
+ self[date_column], format=date_format, errors="coerce"
+ )
+
+
+def transpose_df(
+ df: pd.DataFrame,
+ label_col: str,
+ index_name: str = None,
+ indexes: list = None,
+) -> pd.DataFrame:
+ """
+ Transposes a pandas DataFrame and optionally sets a new index name and
+ custom indexes.
+
+ Args:
+ df (pd.DataFrame): The DataFrame to transpose.
+ label_col (str): The column to set as the index before transposing.
+ index_name (str, optional): The name to set for the index after
+ transposing. Defaults to None.
+ indexes (list, optional): A list of custom indexes to set after
+ transposing. The length of this list must match the number of rows
+ in the transposed DataFrame. Defaults to None.
+
+ Returns:
+ pd.DataFrame: The transposed DataFrame.
+
+ Raises:
+ ValueError: If `label_col` is not a column in the DataFrame.
+ ValueError: If the length of `indexes` does not match the number of
+ rows in the transposed DataFrame.
+ """
+ # Check if the specified column exists in the DataFrame
+ if label_col not in df.columns:
+ raise ValueError(f"Column '{label_col}' not found in DataFrame")
+
+ # Set the specified column as the index
+ transposed_df = df.set_index(label_col).transpose()
+
+ # Set the index name if provided
+ if index_name:
+ transposed_df.columns.name = index_name
+
+ # Set custom indexes if provided
+ if indexes:
+ if len(indexes) != len(transposed_df.index):
+ raise ValueError(
+ "Length of custom indexes must match the number of rows in the transposed DataFrame"
+ )
+ transposed_df.index = indexes
+
+ return transposed_df
+
+
+def pivot_df(df: pd.DataFrame, index: str, columns: str, values: str) -> pd.DataFrame:
+ """
+ Pivots a DataFrame using the specified index, columns, and values.
+
+ Args:
+ df (pd.DataFrame): The DataFrame to pivot.
+ index (str): The column to use for the index.
+ columns (str): The column to use for the columns.
+ values (str): The column to use for the values.
+
+ Returns:
+ pd.DataFrame: The pivoted DataFrame.
+ """
+ df_pivot = df.pivot(index=index, columns=columns, values=values).reset_index()
+ df_pivot.columns = [index] + [f"{col}" for col in df_pivot.columns[1:]]
+ return df_pivot
+
+
+def array_to_df(
+ y_values: Union[ee.Array, ee.List, List[List[float]]],
+ x_values: Optional[Union[ee.Array, ee.List, List[float]]] = None,
+ y_labels: Optional[List[str]] = None,
+ x_label: str = "x",
+ axis: int = 1,
+ **kwargs: Any,
+) -> pd.DataFrame:
+ """
+ Converts arrays or lists of y-values and optional x-values into a pandas DataFrame.
+
+ Args:
+ y_values (Union[ee.Array, ee.List, List[List[float]]]): The y-values to convert.
+ x_values (Optional[Union[ee.Array, ee.List, List[float]]]): The x-values to convert.
+ Defaults to None.
+ y_labels (Optional[List[str]]): The labels for the y-values. Defaults to None.
+ x_label (str): The label for the x-values. Defaults to "x".
+ axis (int): The axis along which to transpose the y-values if needed. Defaults to 1.
+ **kwargs: Additional keyword arguments to pass to the pandas DataFrame constructor.
+
+ Returns:
+ pd.DataFrame: The resulting DataFrame.
+ """
+
+ if isinstance(y_values, ee.Array) or isinstance(y_values, ee.List):
+ y_values = y_values.getInfo()
+
+ if isinstance(x_values, ee.Array) or isinstance(x_values, ee.List):
+ x_values = x_values.getInfo()
+
+ if axis == 0:
+ y_values = np.transpose(y_values)
+
+ if x_values is None:
+ x_values = list(range(1, len(y_values[0]) + 1))
+
+ data = {x_label: x_values}
+
+ if not isinstance(y_values[0], list):
+ y_values = [y_values]
+
+ if y_labels is None:
+ y_labels = [
+ f"y{str(i+1).zfill(len(str(len(y_values))))}" for i in range(len(y_values))
+ ]
+
+ if len(y_labels) != len(y_values):
+ raise ValueError("The length of y_labels must match the length of y_values.")
+
+ for i, series in enumerate(y_labels):
+ data[series] = y_values[i]
+
+ df = pd.DataFrame(data, **kwargs)
+ return df
+
+
+class Chart:
+ """
+ A class to create and display various types of charts from a data table.
+
+ Attributes:
+ data_table (pd.DataFrame): The data to be displayed in the charts.
+ chart_type (str): The type of chart to create. Supported types are
+ 'ScatterChart', 'LineChart', 'ColumnChart', 'BarChart', 'PieChart',
+ 'AreaChart', and 'Table'.
+ chart: The bqplot Figure object for the chart.
+ """
+
+ def __init__(
+ self,
+ data_table: Union[Dict[str, List[Any]], pd.DataFrame],
+ chart_type: str = "LineChart",
+ x_cols: Optional[List[str]] = None,
+ y_cols: Optional[List[str]] = None,
+ colors: Optional[List[str]] = None,
+ title: Optional[str] = None,
+ x_label: Optional[str] = None,
+ y_label: Optional[str] = None,
+ **kwargs: Any,
+ ) -> None:
+ """
+ Initializes the Chart with data.
+
+ Args:
+ data_table (Union[Dict[str, List[Any]], pd.DataFrame]): A 2-D array of data.
+ If it's a dictionary, it will be converted to a DataFrame.
+ chart_type (str): The type of chart to create. Supported types are
+ 'ScatterChart', 'LineChart', 'ColumnChart', 'BarChart',
+ 'PieChart', 'AreaChart', and 'Table'.
+ x_cols (Optional[List[str]]): The columns to use for the x-axis.
+ Defaults to the first column.
+ y_cols (Optional[List[str]]): The columns to use for the y-axis.
+ Defaults to the second column.
+ colors (Optional[List[str]]): The colors to use for the chart.
+ Defaults to a predefined list of colors.
+ title (Optional[str]): The title of the chart. Defaults to the
+ chart type.
+ x_label (Optional[str]): The label for the x-axis. Defaults to an
+ empty string.
+ y_label (Optional[str]): The label for the y-axis. Defaults to an
+ empty string.
+ **kwargs: Additional keyword arguments to pass to the bqplot Figure
+ or mark objects. For axes_options, see
+ https://bqplot.github.io/bqplot/api/axes
+ """
+ self.data_table = DataTable(data_table)
+ self.chart_type = chart_type
+ self.chart = None
+ self.title = title
+ self.x_label = x_label
+ self.y_label = y_label
+ self.x_cols = x_cols
+ self.y_cols = y_cols
+ self.colors = colors
+ self.xlim = kwargs.pop("xlim", None)
+ self.ylim = kwargs.pop("ylim", None)
+
+ if title is not None:
+ kwargs["title"] = title
+ self.figure = plt.figure(**kwargs)
+
+ if chart_type is not None:
+ self.set_chart_type(chart_type, **kwargs)
+
+ def display(self) -> None:
+ """
+ Display the chart without toolbar.
+ """
+ self._set_plt_options()
+ display(self.figure)
+
+ def save_png(self, filepath: str = "chart.png", scale: float = 1.0) -> None:
+ """
+ Save the chart as a PNG image.
+
+ Args:
+ filepath (str): The path to save the PNG image. Defaults to 'chart.png'.
+ scale (float): The scale factor for the image. Defaults to 1.0.
+ """
+ self.figure.save_png(filepath, scale=scale)
+
+ def _ipython_display_(self) -> None:
+ """
+ Display the chart with toolbar.
+ """
+ self._set_plt_options()
+ plt.show()
+
+ def _set_plt_options(self) -> None:
+ """
+ Set the title and labels for the chart.
+ """
+ if self.title is not None:
+ self.figure.title = self.title
+ if self.x_label is not None:
+ plt.xlabel(self.x_label)
+ if self.y_label is not None:
+ plt.ylabel(self.y_label)
+ if self.xlim is not None:
+ plt.xlim(self.xlim[0], self.xlim[1])
+ if self.ylim is not None:
+ plt.ylim(self.ylim[0], self.ylim[1])
+
+ def set_chart_type(
+ self,
+ chart_type: str,
+ clear: bool = True,
+ **kwargs: Any,
+ ) -> None:
+ """
+ Sets the chart type and other chart properties.
+
+ Args:
+ chart_type (str): The type of chart to create. Supported types are
+ 'ScatterChart', 'LineChart', 'ColumnChart', 'BarChart',
+ 'PieChart', 'AreaChart', and 'Table'.
+ clear (bool): Whether to clear the current chart before setting a new one.
+ Defaults to True.
+ **kwargs: Additional keyword arguments to pass to the bqplot Figure
+ or mark objects.
+
+ Returns:
+ Chart: The Chart instance with the chart set.
+ """
+ if clear:
+ plt.clear()
+ self.chart_type = chart_type
+ x_cols = self.x_cols
+ y_cols = self.y_cols
+ colors = self.colors
+
+ if x_cols is None:
+ x_cols = [self.data_table.columns[0]]
+ if y_cols is None:
+ y_cols = [self.data_table.columns[1]]
+
+ if isinstance(x_cols, str):
+ x_cols = [x_cols]
+
+ if isinstance(y_cols, str):
+ y_cols = [y_cols]
+
+ if len(x_cols) == 1 and len(y_cols) > 1:
+ x_cols = x_cols * len(y_cols)
+
+ if "axes_options" not in kwargs:
+ kwargs["axes_options"] = {
+ "x": {"label_offset": "30px"},
+ "y": {"label_offset": "40px"},
+ }
+
+ if chart_type == "PieChart":
+ if colors is None:
+ colors = [
+ "#1f77b4",
+ "#ff7f0e",
+ "#2ca02c",
+ "#d62728",
+ "#9467bd",
+ "#8c564b",
+ "#e377c2",
+ "#7f7f7f",
+ "#bcbd22",
+ "#17becf",
+ ] # Default pie chart colors
+ else:
+ if colors is None:
+ colors = [
+ "blue",
+ "orange",
+ "green",
+ "red",
+ "purple",
+ "brown",
+ ] # Default colors
+
+ if chart_type == "IntervalChart":
+
+ x = self.data_table[x_cols[0]]
+ y = [self.data_table[y_col] for y_col in y_cols]
+ if "fill" not in kwargs:
+ kwargs["fill"] = "between"
+
+ self.chart = plt.plot(
+ x,
+ y,
+ colors=colors,
+ **kwargs,
+ )
+ else:
+ for i, (x_col, y_col) in enumerate(zip(x_cols, y_cols)):
+ color = colors[i % len(colors)]
+ if "display_legend" not in kwargs and len(y_cols) > 1:
+ kwargs["display_legend"] = True
+ kwargs["labels"] = [y_col]
+ else:
+ kwargs["labels"] = [y_col]
+
+ x = self.data_table[x_col]
+ y = self.data_table[y_col]
+
+ if isinstance(x, pd.Series) and (
+ not pd.api.types.is_datetime64_any_dtype(x)
+ ):
+ x = x.tolist()
+ if isinstance(y, pd.Series) and (
+ not pd.api.types.is_datetime64_any_dtype(y)
+ ):
+ y = y.tolist()
+
+ if chart_type == "ScatterChart":
+ self.chart = plt.scatter(
+ x,
+ y,
+ colors=[color],
+ **kwargs,
+ )
+ elif chart_type == "LineChart":
+ self.chart = plt.plot(
+ x,
+ y,
+ colors=[color],
+ **kwargs,
+ )
+ elif chart_type == "AreaChart":
+ if "fill" not in kwargs:
+ kwargs["fill"] = "bottom"
+ self.chart = plt.plot(
+ x,
+ y,
+ colors=[color],
+ **kwargs,
+ )
+ elif chart_type == "ColumnChart":
+ self.chart = plt.bar(
+ x,
+ y,
+ colors=[color],
+ **kwargs,
+ )
+ elif chart_type == "BarChart":
+ if "orientation" not in kwargs:
+ kwargs["orientation"] = "horizontal"
+ self.chart = plt.bar(
+ x,
+ y,
+ colors=[color],
+ **kwargs,
+ )
+ elif chart_type == "AreaChart":
+ if "fill" not in kwargs:
+ kwargs["fill"] = "bottom"
+ self.chart = plt.plot(
+ x,
+ y,
+ colors=[color],
+ **kwargs,
+ )
+ elif chart_type == "PieChart":
+ kwargs.pop("labels", None)
+ self.chart = plt.pie(
+ sizes=y,
+ labels=x,
+ colors=colors[: len(x)],
+ **kwargs,
+ )
+ elif chart_type == "Table":
+ output = widgets.Output(**kwargs)
+ with output:
+ display(self.data_table)
+ output.layout = widgets.Layout(width="50%")
+ display(output)
+ else:
+ self.chart = plt.plot(
+ x,
+ y,
+ colors=[color],
+ **kwargs,
+ )
+
+ self._set_plt_options()
+
+ def get_chart_type(self) -> Optional[str]:
+ """
+ Get the current chart type.
+
+ Returns:
+ Optional[str]: The current chart type, or None if no chart type is set.
+ """
+ return self.chart_type
+
+ def get_data_table(self) -> DataTable:
+ """
+ Get the DataTable used by the chart.
+
+ Returns:
+ DataTable: The DataTable instance containing the chart data.
+ """
+ return self.data_table
+
+ def set_data_table(self, data: Union[Dict[str, List[Any]], pd.DataFrame]) -> None:
+ """
+ Set a new DataTable for the chart.
+
+ Args:
+ data (Union[Dict[str, List[Any]], pd.DataFrame]): The new data to be
+ used for the chart.
+ """
+ self.data_table = DataTable(data)
+
+ def set_options(self, **options: Any) -> None:
+ """
+ Set additional options for the chart.
+
+ Args:
+ **options: Additional options to set for the chart.
+ """
+ for key, value in options.items():
+ setattr(self.figure, key, value)
class BaseChartClass:
"""This should include everything a chart module requires to plot figures."""
- def __init__(self, features, default_labels, name, **kwargs):
+ def __init__(
+ self,
+ features: Union[ee.FeatureCollection, pd.DataFrame],
+ default_labels: List[str],
+ name: str,
+ **kwargs: Any,
+ ):
+ """
+ Initializes the BaseChartClass with the given features, labels, and name.
+
+ Args:
+ features (ee.FeatureCollection | pd.DataFrame): The features to plot.
+ default_labels (List[str]): The default labels for the chart.
+ name (str): The name of the chart.
+ **kwargs: Additional keyword arguments to set as attributes.
+ """
self.ylim = None
self.xlim = None
self.title = ""
@@ -28,14 +528,31 @@ def __init__(self, features, default_labels, name, **kwargs):
self.layout_width = None
self.layout_height = None
self.display_legend = True
- self.xlabel = None
- self.ylabel = None
+ self.x_label = None
+ self.y_label = None
self.labels = default_labels
self.width = None
self.height = None
- self.colors = "black"
self.name = name
+ if isinstance(self.labels, list) and (len(self.labels) > 1):
+ self.colors = [
+ "#604791",
+ "#1d6b99",
+ "#39a8a7",
+ "#0f8755",
+ "#76b349",
+ "#f0af07",
+ "#e37d05",
+ "#cf513e",
+ "#96356f",
+ "#724173",
+ "#9c4f97",
+ "#696969",
+ ]
+ else:
+ self.colors = "black"
+
if isinstance(features, ee.FeatureCollection):
self.df = ee_to_df(features)
elif isinstance(features, pd.DataFrame):
@@ -45,33 +562,72 @@ def __init__(self, features, default_labels, name, **kwargs):
setattr(self, key, value)
@classmethod
- def get_data(self):
+ def get_data(cls) -> None:
+ """
+ Placeholder method to get data for the chart.
+ """
pass
@classmethod
- def plot_chart(self):
+ def plot_chart(cls) -> None:
+ """
+ Placeholder method to plot the chart.
+ """
pass
- def __repr__(self):
+ def __repr__(self) -> str:
+ """
+ Returns the string representation of the chart.
+
+ Returns:
+ str: The name of the chart.
+ """
return self.name
class BarChart(BaseChartClass):
"""Create Bar Chart. All histogram/bar charts can use this object."""
- def __init__(self, features, default_labels, name, type="grouped", **kwargs):
+ def __init__(
+ self,
+ features: Union[ee.FeatureCollection, pd.DataFrame],
+ default_labels: List[str],
+ name: str,
+ type: str = "grouped",
+ **kwargs: Any,
+ ):
+ """
+ Initializes the BarChart with the given features, labels, name, and type.
+
+ Args:
+ features (ee.FeatureCollection | pd.DataFrame): The features to plot.
+ default_labels (List[str]): The default labels for the chart.
+ name (str): The name of the chart.
+ type (str, optional): The type of bar chart ('grouped' or 'stacked').
+ Defaults to 'grouped'.
+ **kwargs: Additional keyword arguments to set as attributes.
+ """
super().__init__(features, default_labels, name, **kwargs)
- self.type = type
+ self.type: str = type
- def generate_tooltip(self):
- if (self.xlabel is not None) and (self.ylabel is not None):
+ def generate_tooltip(self) -> None:
+ """
+ Generates a tooltip for the bar chart.
+ """
+ if (self.x_label is not None) and (self.y_label is not None):
self.bar_chart.tooltip = Tooltip(
- fields=["x", "y"], labels=[self.xlabel, self.ylabel]
+ fields=["x", "y"], labels=[self.x_label, self.y_label]
)
else:
self.bar_chart.tooltip = Tooltip(fields=["x", "y"])
- def get_ylim(self):
+ def get_ylim(self) -> Tuple[float, float]:
+ """
+ Gets the y-axis limits for the bar chart.
+
+ Returns:
+ Tuple[float, float]: The minimum and maximum y-axis limits.
+ """
if self.ylim:
ylim_min, ylim_max = self.ylim[0], self.ylim[1]
else:
@@ -86,7 +642,10 @@ def get_ylim(self):
ylim_max = ylim_max + 0.2 * (ylim_max - ylim_min)
return (ylim_min, ylim_max)
- def plot_chart(self):
+ def plot_chart(self) -> None:
+ """
+ Plots the bar chart.
+ """
fig = plt.figure(
title=self.title,
legend_location=self.legend_location,
@@ -101,8 +660,10 @@ def plot_chart(self):
self.generate_tooltip()
plt.ylim(*self.get_ylim())
- plt.xlabel(self.xlabel)
- plt.ylabel(self.ylabel)
+ if self.x_label:
+ plt.xlabel(self.x_label)
+ if self.y_label:
+ plt.ylabel(self.y_label)
if self.width:
fig.layout.width = self.width
@@ -115,128 +676,288 @@ def plot_chart(self):
plt.show()
+class LineChart(BarChart):
+ """A class to define variables and get_data method for a line chart."""
+
+ def __init__(
+ self,
+ features: Union[ee.FeatureCollection, pd.DataFrame],
+ labels: List[str],
+ name: str = "line.chart",
+ **kwargs: Any,
+ ):
+ """
+ Initializes the LineChart with the given features, labels, and name.
+
+ Args:
+ features (ee.FeatureCollection | pd.DataFrame): The features to plot.
+ labels (List[str]): The labels for the chart.
+ name (str, optional): The name of the chart. Defaults to 'line.chart'.
+ **kwargs: Additional keyword arguments to set as attributes.
+ """
+ super().__init__(features, labels, name, **kwargs)
+
+ def plot_chart(self) -> None:
+ """
+ Plots the line chart.
+ """
+ fig = plt.figure(
+ title=self.title,
+ legend_location=self.legend_location,
+ )
+
+ self.line_chart = plt.plot(
+ self.x_data,
+ self.y_data,
+ label=self.labels,
+ )
+
+ self.generate_tooltip()
+ plt.ylim(*self.get_ylim())
+ if self.x_label:
+ plt.xlabel(self.x_label)
+ if self.y_label:
+ plt.ylabel(self.y_label)
+
+ if self.width:
+ fig.layout.width = self.width
+ if self.height:
+ fig.layout.height = self.height
+
+ plt.show()
+
+
class Feature_ByFeature(BarChart):
- """A object to define variables and get_data method."""
+ """An object to define variables and get_data method for features by feature."""
def __init__(
- self, features, xProperty, yProperties, name="feature.byFeature", **kwargs
+ self,
+ features: Union[ee.FeatureCollection, pd.DataFrame],
+ x_property: str,
+ y_properties: List[str],
+ name: str = "feature.byFeature",
+ **kwargs: Any,
):
- default_labels = yProperties
+ """
+ Initializes the Feature_ByFeature with the given features, x_property,
+ y_properties, and name.
+
+ Args:
+ features (ee.FeatureCollection | pd.DataFrame): The features to plot.
+ x_property (str): The property to use for the x-axis.
+ y_properties (List[str]): The properties to use for the y-axis.
+ name (str, optional): The name of the chart. Defaults to
+ 'feature.byFeature'.
+ **kwargs: Additional keyword arguments to set as attributes.
+ """
+ default_labels = y_properties
super().__init__(features, default_labels, name, **kwargs)
- self.x_data, self.y_data = self.get_data(xProperty, yProperties)
-
- def get_data(self, xProperty, yProperties):
- x_data = list(self.df[xProperty])
- y_data = list(self.df[yProperties].values.T)
+ self.x_data, self.y_data = self.get_data(x_property, y_properties)
+
+ def get_data(
+ self, x_property: str, y_properties: List[str]
+ ) -> Tuple[List[Any], List[Any]]:
+ """
+ Gets the data for the chart.
+
+ Args:
+ x_property (str): The property to use for the x-axis.
+ y_properties (List[str]): The properties to use for the y-axis.
+
+ Returns:
+ Tuple[List[Any], List[Any]]: The x and y data for the chart.
+ """
+ x_data = list(self.df[x_property])
+ y_data = list(self.df[y_properties].values.T)
return x_data, y_data
class Feature_ByProperty(BarChart):
- """A object to define variables and get_data method."""
+ """An object to define variables and get_data method for features by property."""
def __init__(
- self, features, xProperties, seriesProperty, name="feature.byProperty", **kwargs
+ self,
+ features: Union[ee.FeatureCollection, pd.DataFrame],
+ x_properties: Union[List[str], Dict[str, str]],
+ series_property: str,
+ name: str = "feature.byProperty",
+ **kwargs: Any,
):
+ """
+ Initializes the Feature_ByProperty with the given features, x_properties,
+ series_property, and name.
+
+ Args:
+ features (ee.FeatureCollection | pd.DataFrame): The features to plot.
+ x_properties (List[str] | Dict[str, str]): The properties to use for
+ the x-axis.
+ series_property (str): The property to use for labeling the series.
+ name (str, optional): The name of the chart. Defaults to
+ 'feature.byProperty'.
+ **kwargs: Additional keyword arguments to set as attributes.
+
+ Raises:
+ Exception: If 'labels' is in kwargs.
+ """
default_labels = None
super().__init__(features, default_labels, name, **kwargs)
if "labels" in kwargs:
raise Exception("Please remove labels in kwargs and try again.")
- self.labels = list(self.df[seriesProperty])
- self.x_data, self.y_data = self.get_data(xProperties)
-
- def get_data(self, xProperties):
- if isinstance(xProperties, list):
- x_data = xProperties
- y_data = self.df[xProperties].values
- elif isinstance(xProperties, dict):
- x_data = list(xProperties.values())
- y_data = self.df[list(xProperties.keys())].values
+ self.labels = list(self.df[series_property])
+ self.x_data, self.y_data = self.get_data(x_properties)
+
+ def get_data(
+ self, x_properties: Union[List[str], Dict[str, str]]
+ ) -> Tuple[List[Any], List[Any]]:
+ """
+ Gets the data for the chart.
+
+ Args:
+ x_properties (List[str] | Dict[str, str]): The properties to use for
+ the x-axis.
+
+ Returns:
+ Tuple[List[Any], List[Any]]: The x and y data for the chart.
+
+ Raises:
+ Exception: If x_properties is not a list or dictionary.
+ """
+ if isinstance(x_properties, list):
+ x_data = x_properties
+ y_data = self.df[x_properties].values
+ elif isinstance(x_properties, dict):
+ x_data = list(x_properties.values())
+ y_data = self.df[list(x_properties.keys())].values
else:
- raise Exception("xProperties must be a list or dictionary.")
+ raise Exception("x_properties must be a list or dictionary.")
return x_data, y_data
class Feature_Groups(BarChart):
- """A object to define variables and get_data method."""
+ """An object to define variables and get_data method for feature groups."""
def __init__(
self,
- features,
- xProperty,
- yProperty,
- seriesProperty,
- name="feature.groups",
- type="stacked",
- **kwargs,
+ features: Union[ee.FeatureCollection, pd.DataFrame],
+ x_property: str,
+ y_property: str,
+ series_property: str,
+ name: str = "feature.groups",
+ type: str = "stacked",
+ **kwargs: Any,
):
+ """
+ Initializes the Feature_Groups with the given features, x_property,
+ y_property, series_property, name, and type.
+
+ Args:
+ features (ee.FeatureCollection | pd.DataFrame): The features to plot.
+ x_property (str): The property to use for the x-axis.
+ y_property (str): The property to use for the y-axis.
+ series_property (str): The property to use for labeling the series.
+ name (str, optional): The name of the chart. Defaults to 'feature.groups'.
+ type (str, optional): The type of bar chart ('grouped' or 'stacked').
+ Defaults to 'stacked'.
+ **kwargs: Additional keyword arguments to set as attributes.
+ """
df = ee_to_df(features)
- self.unique_series_values = df[seriesProperty].unique().tolist()
+ self.unique_series_values = df[series_property].unique().tolist()
default_labels = [str(x) for x in self.unique_series_values]
- self.yProperty = yProperty
+ self.yProperty = y_property
super().__init__(features, default_labels, name, type, **kwargs)
- self.new_column_names = self.get_column_names(seriesProperty, yProperty)
- self.x_data, self.y_data = self.get_data(xProperty, self.new_column_names)
+ self.new_column_names = self.get_column_names(series_property, y_property)
+ self.x_data, self.y_data = self.get_data(x_property, self.new_column_names)
+
+ def get_column_names(self, series_property: str, y_property: str) -> List[str]:
+ """
+ Gets the new column names for the DataFrame.
- def get_column_names(self, seriesProperty, yProperty):
+ Args:
+ series_property (str): The property to use for labeling the series.
+ y_property (str): The property to use for the y-axis.
+
+ Returns:
+ List[str]: The new column names.
+ """
new_column_names = []
for value in self.unique_series_values:
- sample_filter = (self.df[seriesProperty] == value).map({True: 1, False: 0})
- column_name = str(yProperty) + "_" + str(value)
- self.df[column_name] = self.df[yProperty] * sample_filter
+ sample_filter = (self.df[series_property] == value).map({True: 1, False: 0})
+ column_name = str(y_property) + "_" + str(value)
+ self.df[column_name] = self.df[y_property] * sample_filter
new_column_names.append(column_name)
return new_column_names
- def get_data(self, xProperty, new_column_names):
- x_data = list(self.df[xProperty])
+ def get_data(
+ self, x_property: str, new_column_names: List[str]
+ ) -> Tuple[List[Any], List[Any]]:
+ """
+ Gets the data for the chart.
+
+ Args:
+ x_property (str): The property to use for the x-axis.
+ new_column_names (List[str]): The new column names for the y-axis.
+
+ Returns:
+ Tuple[List[Any], List[Any]]: The x and y data for the chart.
+ """
+ x_data = list(self.df[x_property])
y_data = [self.df[x] for x in new_column_names]
return x_data, y_data
-def feature_byFeature(
- features: ee.FeatureCollection, xProperty: str, yProperties: list, **kwargs
-):
- """Generates a Chart from a set of features. Plots the value of one or more properties for each feature.
+def feature_by_feature(
+ features: ee.FeatureCollection,
+ x_property: str,
+ y_properties: List[str],
+ **kwargs: Any,
+) -> None:
+ """
+ Generates a Chart from a set of features. Plots the value of one or more
+ properties for each feature.
Reference: https://developers.google.com/earth-engine/guides/charts_feature#uichartfeaturebyfeature
Args:
features (ee.FeatureCollection): The feature collection to generate a chart from.
- xProperty (str): Features labeled by xProperty.
- yProperties (list): Values of yProperties.
+ x_property (str): Features labeled by x_property.
+ y_properties (List[str]): Values of y_properties.
+ **kwargs: Additional keyword arguments to set as attributes.
Raises:
Exception: Errors when creating the chart.
"""
bar = Feature_ByFeature(
- features=features, xProperty=xProperty, yProperties=yProperties, **kwargs
+ features=features, x_property=x_property, y_properties=y_properties, **kwargs
)
try:
bar.plot_chart()
-
except Exception as e:
raise Exception(e)
-def feature_byProperty(
+def feature_by_property(
features: ee.FeatureCollection,
- xProperties: Union[list, dict],
- seriesProperty: str,
+ x_properties: Union[list, dict],
+ series_property: str,
**kwargs,
):
- """Generates a Chart from a set of features. Plots property values of one or more features.
+ """Generates a Chart from a set of features. Plots property values of one or
+ more features.
Reference: https://developers.google.com/earth-engine/guides/charts_feature#uichartfeaturebyproperty
Args:
features (ee.FeatureCollection): The features to include in the chart.
- xProperties (list | dict): One of (1) a list of properties to be plotted on the x-axis; or
- (2) a (property, label) dictionary specifying labels for properties to be used as values on the x-axis.
- seriesProperty (str): The name of the property used to label each feature in the legend.
+ x_properties (list | dict): One of (1) a list of properties to be
+ plotted on the x-axis; or (2) a (property, label) dictionary
+ specifying labels for properties to be used as values on the x-axis.
+ series_property (str): The name of the property used to label each
+ feature in the legend.
Raises:
Exception: If the provided xProperties is not a list or dict.
@@ -244,8 +965,8 @@ def feature_byProperty(
"""
bar = Feature_ByProperty(
features=features,
- xProperties=xProperties,
- seriesProperty=seriesProperty,
+ x_properties=x_properties,
+ series_property=series_property,
**kwargs,
)
@@ -256,25 +977,36 @@ def feature_byProperty(
raise Exception(e)
-def feature_groups(features, xProperty, yProperty, seriesProperty, **kwargs):
- """Generates a Chart from a set of features.
+def feature_groups(
+ features: ee.FeatureCollection,
+ x_property: str,
+ y_property: str,
+ series_property: str,
+ **kwargs: Any,
+) -> None:
+ """
+ Generates a Chart from a set of features.
Plots the value of one property for each feature.
+
Reference:
https://developers.google.com/earth-engine/guides/charts_feature#uichartfeaturegroups
+
Args:
features (ee.FeatureCollection): The feature collection to make a chart from.
- xProperty (str): Features labeled by xProperty.
- yProperty (str): Features labeled by yProperty.
- seriesProperty (str): The property used to label each feature in the legend.
+ x_property (str): Features labeled by xProperty.
+ y_property (str): Features labeled by yProperty.
+ series_property (str): The property used to label each feature in the legend.
+ **kwargs: Additional keyword arguments to set as attributes.
+
Raises:
Exception: Errors when creating the chart.
"""
bar = Feature_Groups(
features=features,
- xProperty=xProperty,
- yProperty=yProperty,
- seriesProperty=seriesProperty,
+ x_property=x_property,
+ y_property=y_property,
+ series_property=series_property,
**kwargs,
)
@@ -286,8 +1018,13 @@ def feature_groups(features, xProperty, yProperty, seriesProperty, **kwargs):
def feature_histogram(
- features, property, maxBuckets=None, minBucketWidth=None, show=True, **kwargs
-):
+ features: ee.FeatureCollection,
+ property: str,
+ max_buckets: Optional[int] = None,
+ min_bucket_width: Optional[float] = None,
+ show: bool = True,
+ **kwargs: Any,
+) -> Optional[Any]:
"""
Generates a Chart from a set of features.
Computes and plots a histogram of the given property.
@@ -298,16 +1035,23 @@ def feature_histogram(
https://developers.google.com/earth-engine/guides/charts_feature#uichartfeaturehistogram
Args:
- features (ee.FeatureCollection): The features to include in the chart.
- property (str): The name of the property to generate the histogram for.
- maxBuckets (int, optional): The maximum number of buckets (bins) to use when building a histogram;
- will be rounded up to a power of 2.
- minBucketWidth (float, optional): The minimum histogram bucket width, or null to allow any power of 2.
- show (bool, optional): Whether to show the chart. If not, it will return the bqplot chart object, which can be used to retrieve data for the chart. Defaults to True.
+ features (ee.FeatureCollection): The features to include in the chart.
+ property (str): The name of the property to generate the histogram for.
+ max_buckets (int, optional): The maximum number of buckets (bins) to use
+ when building a histogram; will be rounded up to a power of 2.
+ min_bucket_width (float, optional): The minimum histogram bucket width,
+ or null to allow any power of 2.
+ show (bool, optional): Whether to show the chart. If not, it will return
+ the bqplot chart object, which can be used to retrieve data for the
+ chart. Defaults to True.
+ **kwargs: Additional keyword arguments to set as attributes.
Raises:
Exception: If the provided xProperties is not a list or dict.
Exception: If the chart fails to create.
+
+ Returns:
+ Optional[Any]: The bqplot chart object if show is False, otherwise None.
"""
import math
@@ -344,22 +1088,22 @@ def grow_bin(bin_size, ref):
data_range = max_value - min_value
- if not maxBuckets:
+ if not max_buckets:
initial_bin_size = nextPowerOf2(data_range / pow(2, 8))
- if minBucketWidth:
- if minBucketWidth < initial_bin_size:
- bin_size = grow_bin(minBucketWidth, initial_bin_size)
+ if min_bucket_width:
+ if min_bucket_width < initial_bin_size:
+ bin_size = grow_bin(min_bucket_width, initial_bin_size)
else:
- bin_size = minBucketWidth
+ bin_size = min_bucket_width
else:
bin_size = initial_bin_size
else:
- initial_bin_size = math.ceil(data_range / nextPowerOf2(maxBuckets))
- if minBucketWidth:
- if minBucketWidth < initial_bin_size:
- bin_size = grow_bin(minBucketWidth, initial_bin_size)
+ initial_bin_size = math.ceil(data_range / nextPowerOf2(max_buckets))
+ if min_bucket_width:
+ if min_bucket_width < initial_bin_size:
+ bin_size = grow_bin(min_bucket_width, initial_bin_size)
else:
- bin_size = minBucketWidth
+ bin_size = min_bucket_width
else:
bin_size = initial_bin_size
@@ -389,20 +1133,20 @@ def grow_bin(bin_size, ref):
if "height" in kwargs:
fig.layout.height = kwargs["height"]
- if "xlabel" not in kwargs:
- xlabel = ""
+ if "x_label" not in kwargs:
+ x_label = ""
else:
- xlabel = kwargs["xlabel"]
+ x_label = kwargs["x_label"]
- if "ylabel" not in kwargs:
- ylabel = ""
+ if "y_label" not in kwargs:
+ y_label = ""
else:
- ylabel = kwargs["ylabel"]
+ y_label = kwargs["y_label"]
histogram = plt.hist(
sample=y_data,
bins=num_bins,
- axes_options={"count": {"label": ylabel}, "sample": {"label": xlabel}},
+ axes_options={"count": {"label": y_label}, "sample": {"label": x_label}},
)
if "colors" in kwargs:
@@ -416,10 +1160,10 @@ def grow_bin(bin_size, ref):
else:
histogram.stroke_width = 0
- if ("xlabel" in kwargs) and ("ylabel" in kwargs):
+ if ("x_label" in kwargs) and ("y_label" in kwargs):
histogram.tooltip = Tooltip(
fields=["midpoint", "count"],
- labels=[kwargs["xlabel"], kwargs["ylabel"]],
+ labels=[kwargs["x_label"], kwargs["y_label"]],
)
else:
histogram.tooltip = Tooltip(fields=["midpoint", "count"])
@@ -433,82 +1177,851 @@ def grow_bin(bin_size, ref):
raise Exception(e)
-def image_byClass(
- image, classBand, region, reducer, scale, classLabels, xLabels, **kwargs
-):
- # TODO
- pass
+def image_by_class(
+ image: ee.Image,
+ class_band: str,
+ region: Union[ee.Geometry, ee.FeatureCollection],
+ reducer: Union[str, ee.Reducer] = "MEAN",
+ scale: Optional[int] = None,
+ class_labels: Optional[List[str]] = None,
+ x_labels: Optional[List[str]] = None,
+ chart_type: str = "LineChart",
+ **kwargs: Any,
+) -> Any:
+ """
+ Generates a Chart from an image by class. Extracts and plots band values by class.
+ Args:
+ image (ee.Image): Image to extract band values from.
+ class_band (str): The band name to use as class labels.
+ region (ee.Geometry | ee.FeatureCollection): The region(s) to reduce.
+ reducer (str | ee.Reducer, optional): The reducer type for zonal statistics. Can
+ be one of 'mean', 'median', 'sum', 'min', 'max', etc. Defaults to 'MEAN'.
+ scale (int, optional): The scale in meters at which to perform the analysis.
+ class_labels (List[str], optional): List of class labels.
+ x_labels (List[str], optional): List of x-axis labels.
+ chart_type (str, optional): The type of chart to create. Supported types are
+ 'ScatterChart', 'LineChart', 'ColumnChart', 'BarChart', 'PieChart',
+ 'AreaChart', and 'Table'. Defaults to 'LineChart'.
+ **kwargs: Additional keyword arguments.
+
+ Returns:
+ Any: The generated chart.
+ """
+ fc = zonal_stats(
+ image, region, stat_type=reducer, scale=scale, verbose=False, return_fc=True
+ )
+ bands = image.bandNames().getInfo()
+ df = ee_to_df(fc)[bands + [class_band]]
-def image_byRegion(image, regions, reducer, scale, xProperty, **kwargs):
- # TODO
- pass
+ df_transposed = df.set_index(class_band).T
+ if x_labels is not None:
+ df_transposed["label"] = x_labels
+ else:
+ df_transposed["label"] = df_transposed.index
-def image_doySeries(
- imageCollection,
- region,
- regionReducer,
- scale,
- yearReducer,
- startDay,
- endDay,
- **kwargs,
-):
- # TODO
- pass
-
-
-def image_doySeriesByRegion(
- imageCollection,
- bandName,
- regions,
- regionReducer,
- scale,
- yearReducer,
- seriesProperty,
- startDay,
- endDay,
- **kwargs,
-):
- # TODO
- pass
-
-
-def image_doySeriesByYear(
- imageCollection,
- bandName,
- region,
- regionReducer,
- scale,
- sameDayReducer,
- startDay,
- endDay,
- **kwargs,
-):
- # TODO
- pass
+ if class_labels is None:
+ y_cols = df_transposed.columns.tolist()
+ y_cols.remove("label")
+ else:
+ y_cols = class_labels
+
+ fig = Chart(
+ df_transposed, chart_type=chart_type, x_cols="label", y_cols=y_cols, **kwargs
+ )
+ return fig
+
+
+def image_by_region(
+ image: ee.Image,
+ regions: Union[ee.FeatureCollection, ee.Geometry],
+ reducer: Union[str, ee.Reducer],
+ scale: int,
+ x_property: str,
+ **kwargs: Any,
+) -> None:
+ """
+ Generates a Chart from an image. Extracts and plots band values in one or more
+ regions in the image, with each band in a separate series.
+
+ Args:
+ image (ee.Image): Image to extract band values from.
+ regions (ee.FeatureCollection | ee.Geometry): Regions to reduce.
+ Defaults to the image's footprint.
+ reducer (str | ee.Reducer): The reducer type for zonal statistics. Can
+ be one of 'mean', 'median', 'sum', 'min', 'max', etc.
+ scale (int): The scale in meters at which to perform the analysis.
+ x_property (str): The name of the property in the feature collection to
+ use as the x-axis values.
+ **kwargs: Additional keyword arguments to be passed to the
+ `feature_by_feature` function.
+
+ Returns:
+ None
+ """
+
+ fc = zonal_stats(
+ image, regions, stat_type=reducer, scale=scale, verbose=False, return_fc=True
+ )
+ bands = image.bandNames().getInfo()
+ df = ee_to_df(fc)[bands + [x_property]]
+ feature_by_feature(df, x_property, bands, **kwargs)
+
+
+def image_doy_series(
+ image_collection: ee.ImageCollection,
+ region: Optional[Union[ee.Geometry, ee.FeatureCollection]] = None,
+ region_reducer: Optional[Union[str, ee.Reducer]] = None,
+ scale: Optional[int] = None,
+ year_reducer: Optional[Union[str, ee.Reducer]] = None,
+ start_day: int = 1,
+ end_day: int = 366,
+ chart_type: str = "LineChart",
+ colors: Optional[List[str]] = None,
+ title: Optional[str] = None,
+ x_label: Optional[str] = None,
+ y_label: Optional[str] = None,
+ **kwargs: Any,
+) -> Chart:
+ """
+ Generates a time series chart of an image collection for a specific region
+ over a range of days of the year.
+
+ Args:
+ image_collection (ee.ImageCollection): The image collection to analyze.
+ region (Optional[Union[ee.Geometry, ee.FeatureCollection]]): The region
+ to reduce.
+ region_reducer (Optional[Union[str, ee.Reducer]]): The reducer type for
+ zonal statistics.Can be one of 'mean', 'median', 'sum', 'min', 'max', etc.
+ scale (Optional[int]): The scale in meters at which to perform the analysis.
+ year_reducer (Optional[Union[str, ee.Reducer]]): The reducer type for
+ yearly statistics.
+ start_day (int): The start day of the year.
+ end_day (int): The end day of the year.
+ chart_type (str): The type of chart to create. Supported types are
+ 'ScatterChart', 'LineChart', 'ColumnChart', 'BarChart',
+ 'PieChart', 'AreaChart', and 'Table'.
+ colors (Optional[List[str]]): The colors to use for the chart.
+ Defaults to a predefined list of colors.
+ title (Optional[str]): The title of the chart. Defaults to the
+ chart type.
+ x_label (Optional[str]): The label for the x-axis. Defaults to an
+ empty string.
+ y_label (Optional[str]): The label for the y-axis. Defaults to an
+ empty string.
+ **kwargs: Additional keyword arguments to pass to the bqplot Figure
+ or mark objects. For axes_options, see
+ https://bqplot.github.io/bqplot/api/axes
+
+ Returns:
+ Chart: The generated chart.
+ """
+
+ # Function to add day-of-year ('doy') and year properties to each image.
+ def set_doys(collection):
+ def add_doy(img):
+ date = img.date()
+ year = date.get("year")
+ doy = date.getRelative("day", "year").floor().add(1)
+ return img.set({"doy": doy, "year": year})
+
+ return collection.map(add_doy)
+
+ # Reduces images with the same day of year.
+ def group_by_doy(collection, start, end, reducer):
+ collection = set_doys(collection)
+
+ doys = ee.FeatureCollection(
+ [ee.Feature(None, {"doy": i}) for i in range(start, end + 1)]
+ )
+
+ # Group images by their day of year.
+ filter = ee.Filter(ee.Filter.equals(leftField="doy", rightField="doy"))
+ joined = ee.Join.saveAll("matches").apply(
+ primary=doys, secondary=collection, condition=filter
+ )
+
+ # For each DoY, reduce images across years.
+ def reduce_images(doy):
+ images = ee.ImageCollection.fromImages(doy.get("matches"))
+ image = images.reduce(reducer)
+ return image.set(
+ {
+ "doy": doy.get("doy"),
+ "geo": images.geometry(), # // Retain geometry for future reduceRegion.
+ }
+ )
+
+ return ee.ImageCollection(joined.map(reduce_images))
+
+ # Set default values and filters if parameters are not provided.
+ region_reducer = region_reducer or ee.Reducer.mean()
+ year_reducer = year_reducer or ee.Reducer.mean()
+
+ # Optionally filter the image collection by region.
+ filtered_collection = image_collection
+ if region:
+ filtered_collection = filtered_collection.filterBounds(region)
+ filtered_collection = set_doys(filtered_collection)
+
+ doy_images = group_by_doy(filtered_collection, start_day, end_day, year_reducer)
+
+ # For each DoY, reduce images across years within the region.
+ def reduce_doy_images(image):
+ region_for_image = region if region else image.get("geo")
+ dictionary = image.reduceRegion(
+ reducer=region_reducer, geometry=region_for_image, scale=scale
+ )
+
+ return ee.Feature(None, {"doy": image.get("doy")}).set(dictionary)
+
+ reduced = ee.FeatureCollection(doy_images.map(reduce_doy_images))
+
+ df = ee_to_df(reduced)
+ df.columns = df.columns.str.replace(r"_.*", "", regex=True)
+
+ x_cols = "doy"
+ y_cols = df.columns.tolist()
+ y_cols.remove("doy")
+
+ fig = Chart(
+ df,
+ chart_type,
+ x_cols,
+ y_cols,
+ colors,
+ title,
+ x_label,
+ y_label,
+ **kwargs,
+ )
+ return fig
+
+
+def image_doy_series_by_region(
+ image_collection: ee.ImageCollection,
+ band_name: str,
+ regions: ee.FeatureCollection,
+ region_reducer: Optional[Union[str, ee.Reducer]] = None,
+ scale: Optional[int] = None,
+ year_reducer: Optional[Union[str, ee.Reducer]] = None,
+ series_property: Optional[str] = None,
+ start_day: int = 1,
+ end_day: int = 366,
+ chart_type: str = "LineChart",
+ colors: Optional[List[str]] = None,
+ title: Optional[str] = None,
+ x_label: Optional[str] = None,
+ y_label: Optional[str] = None,
+ **kwargs: Any,
+) -> Chart:
+ """
+ Generates a time series chart of an image collection for multiple regions
+ over a range of days of the year.
+
+ Args:
+ image_collection (ee.ImageCollection): The image collection to analyze.
+ band_name (str): The name of the band to analyze.
+ regions (ee.FeatureCollection): The regions to analyze.
+ region_reducer (Optional[Union[str, ee.Reducer]]): The reducer type for
+ zonal statistics.
+ scale (Optional[int]): The scale in meters at which to perform the analysis.
+ year_reducer (Optional[Union[str, ee.Reducer]]): The reducer type for
+ yearly statistics.
+ series_property (Optional[str]): The property to use for labeling the series.
+ start_day (int): The start day of the year.
+ end_day (int): The end day of the year.
+ chart_type (str): The type of chart to create. Supported types are
+ 'ScatterChart', 'LineChart', 'ColumnChart', 'BarChart',
+ 'PieChart', 'AreaChart', and 'Table'.
+ colors (Optional[List[str]]): The colors to use for the chart.
+ Defaults to a predefined list of colors.
+ title (Optional[str]): The title of the chart. Defaults to the
+ chart type.
+ x_label (Optional[str]): The label for the x-axis. Defaults to an
+ empty string.
+ y_label (Optional[str]): The label for the y-axis. Defaults to an
+ empty string.
+ **kwargs: Additional keyword arguments to pass to the bqplot Figure
+ or mark objects. For axes_options, see
+ https://bqplot.github.io/bqplot/api/axes
+
+ Returns:
+ Chart: The generated chart.
+ """
+
+ image_collection = image_collection.select(band_name)
+
+ # Function to add day-of-year ('doy') and year properties to each image.
+ def set_doys(collection):
+ def add_doy(img):
+ date = img.date()
+ year = date.get("year")
+ doy = date.getRelative("day", "year").floor().add(1)
+ return img.set({"doy": doy, "year": year})
+
+ return collection.map(add_doy)
+
+ # Reduces images with the same day of year.
+ def group_by_doy(collection, start, end, reducer):
+ collection = set_doys(collection)
+
+ doys = ee.FeatureCollection(
+ [ee.Feature(None, {"doy": i}) for i in range(start, end + 1)]
+ )
+
+ # Group images by their day of year.
+ filter = ee.Filter(ee.Filter.equals(leftField="doy", rightField="doy"))
+ joined = ee.Join.saveAll("matches").apply(
+ primary=doys, secondary=collection, condition=filter
+ )
+
+ # For each DoY, reduce images across years.
+ def reduce_images(doy):
+ images = ee.ImageCollection.fromImages(doy.get("matches"))
+ image = images.reduce(reducer)
+ return image.set(
+ {
+ "doy": doy.get("doy"),
+ "geo": images.geometry(), # // Retain geometry for future reduceRegion.
+ }
+ )
+
+ return ee.ImageCollection(joined.map(reduce_images))
+
+ if year_reducer is None:
+ year_reducer = ee.Reducer.mean()
+ if region_reducer is None:
+ region_reducer = ee.Reducer.mean()
+
+ doy_images = group_by_doy(image_collection, start_day, end_day, year_reducer)
+
+ if series_property is None:
+ series_property = "system:index"
+ regions = regions.select([series_property])
+ fc = zonal_stats(
+ doy_images.toBands(),
+ regions,
+ stat_type=region_reducer,
+ scale=scale,
+ verbose=False,
+ return_fc=True,
+ )
+ df = ee_to_df(fc)
+ df = transpose_df(df, label_col=series_property, index_name="doy")
+ df["doy"] = df.index.str.split("_").str[0].astype(int)
+ df.sort_values("doy", inplace=True)
+ y_cols = df.columns.tolist()
+ y_cols.remove("doy")
+
+ fig = Chart(
+ df,
+ chart_type,
+ "doy",
+ y_cols,
+ colors,
+ title,
+ x_label,
+ y_label,
+ **kwargs,
+ )
+ return fig
+
+
+def doy_series_by_year(
+ image_collection: ee.ImageCollection,
+ band_name: str,
+ region: Optional[Union[ee.Geometry, ee.FeatureCollection]] = None,
+ region_reducer: Optional[Union[str, ee.Reducer]] = None,
+ scale: Optional[int] = None,
+ same_day_reducer: Optional[Union[str, ee.Reducer]] = None,
+ start_day: int = 1,
+ end_day: int = 366,
+ chart_type: str = "LineChart",
+ colors: Optional[List[str]] = None,
+ title: Optional[str] = None,
+ x_label: Optional[str] = None,
+ y_label: Optional[str] = None,
+ **kwargs: Any,
+) -> Chart:
+ """
+ Generates a time series chart of an image collection for a specific region
+ over multiple years.
+
+ Args:
+ image_collection (ee.ImageCollection): The image collection to analyze.
+ band_name (str): The name of the band to analyze.
+ region (Optional[Union[ee.Geometry, ee.FeatureCollection]]): The region
+ to analyze.
+ region_reducer (Optional[Union[str, ee.Reducer]]): The reducer type for
+ zonal statistics.
+ scale (Optional[int]): The scale in meters at which to perform the analysis.
+ same_day_reducer (Optional[Union[str, ee.Reducer]]): The reducer type
+ for daily statistics.
+ start_day (int): The start day of the year.
+ end_day (int): The end day of the year.
+ chart_type (str): The type of chart to create. Supported types are
+ 'ScatterChart', 'LineChart', 'ColumnChart', 'BarChart',
+ 'PieChart', 'AreaChart', and 'Table'.
+ colors (Optional[List[str]]): The colors to use for the chart.
+ Defaults to a predefined list of colors.
+ title (Optional[str]): The title of the chart. Defaults to the
+ chart type.
+ x_label (Optional[str]): The label for the x-axis. Defaults to an
+ empty string.
+ y_label (Optional[str]): The label for the y-axis. Defaults to an
+ empty string.
+ **kwargs: Additional keyword arguments to pass to the bqplot Figure
+ or mark objects. For axes_options, see
+ https://bqplot.github.io/bqplot/api/axes
+
+ Returns:
+ Chart: The generated chart.
+ """
+
+ # Function to add day-of-year ('doy') and year properties to each image.
+ def set_doys(collection):
+ def add_doy(img):
+ date = img.date()
+ year = date.get("year")
+ doy = date.getRelative("day", "year").floor().add(1)
+ return img.set({"doy": doy, "year": year})
+
+ return collection.map(add_doy)
+
+ # Set default values and filters if parameters are not provided.
+ region_reducer = region_reducer or ee.Reducer.mean()
+ same_day_reducer = same_day_reducer or ee.Reducer.mean()
+
+ # Optionally filter the image collection by region.
+ filtered_collection = image_collection
+ if region:
+ filtered_collection = filtered_collection.filterBounds(region)
+ filtered_collection = set_doys(filtered_collection)
+
+ # Filter image collection by day of year.
+ filtered_collection = filtered_collection.filter(
+ ee.Filter.calendarRange(start_day, end_day, "day_of_year")
+ )
+
+ # Generate a feature for each (doy, value, year) combination.
+ def create_feature(image):
+ value = (
+ image.select(band_name)
+ .reduceRegion(reducer=region_reducer, geometry=region, scale=scale)
+ .get(band_name)
+ ) # Get the reduced value for the given band.
+ return ee.Feature(
+ None, {"doy": image.get("doy"), "year": image.get("year"), "value": value}
+ )
+
+ tuples = filtered_collection.map(create_feature)
+
+ # Group by unique (doy, year) pairs.
+ distinct_doy_year = tuples.distinct(["doy", "year"])
+
+ # Join the original tuples with the distinct (doy, year) pairs.
+ filter = ee.Filter.And(
+ ee.Filter.equals(leftField="doy", rightField="doy"),
+ ee.Filter.equals(leftField="year", rightField="year"),
+ )
+ joined = ee.Join.saveAll("matches").apply(
+ primary=distinct_doy_year, secondary=tuples, condition=filter
+ )
+
+ # For each (doy, year), reduce the values of the joined features.
+ def reduce_features(doy_year):
+ features = ee.FeatureCollection(ee.List(doy_year.get("matches")))
+ value = features.aggregate_array("value").reduce(same_day_reducer)
+ return doy_year.set("value", value)
+
+ reduced = joined.map(reduce_features)
+
+ df = ee_to_df(reduced, columns=["doy", "year", "value"])
+ df = pivot_df(df, index="doy", columns="year", values="value")
+ y_cols = df.columns.tolist()[1:]
+ x_cols = "doy"
+
+ fig = Chart(
+ df,
+ chart_type,
+ x_cols,
+ y_cols,
+ colors,
+ title,
+ x_label,
+ y_label,
+ **kwargs,
+ )
+ return fig
def image_histogram(
- image, region, scale, maxBuckets, minBucketWidth, maxRaw, maxPixels, **kwargs
-):
- # TODO
- pass
+ image: ee.Image,
+ region: ee.Geometry,
+ scale: int,
+ max_buckets: int,
+ min_bucket_width: float,
+ max_raw: int,
+ max_pixels: int,
+ reducer_args: Dict[str, Any] = {},
+ **kwargs: Dict[str, Any],
+) -> bq.Figure:
+ """
+ Creates a histogram for each band of the specified image within the given
+ region using bqplot.
+ Args:
+ image (ee.Image): The Earth Engine image for which to create histograms.
+ region (ee.Geometry): The region over which to calculate the histograms.
+ scale (int): The scale in meters of the calculation.
+ max_buckets (int): The maximum number of buckets in the histogram.
+ min_bucket_width (float): The minimum width of the buckets in the histogram.
+ max_raw (int): The maximum number of pixels to include in the histogram.
+ max_pixels (int): The maximum number of pixels to reduce.
+ reducer_args (Dict[str, Any]): Additional arguments to pass to the image.reduceRegion.
+
+ Keyword Args:
+ colors (List[str]): Colors for the histograms of each band.
+ labels (List[str]): Labels for the histograms of each band.
+ title (str): Title of the combined histogram plot.
+ legend_location (str): Location of the legend in the plot.
+
+ Returns:
+ bq.Figure: The bqplot figure containing the histograms.
+ """
+ # Calculate the histogram data.
+ histogram = image.reduceRegion(
+ reducer=ee.Reducer.histogram(
+ maxBuckets=max_buckets, minBucketWidth=min_bucket_width, maxRaw=max_raw
+ ),
+ geometry=region,
+ scale=scale,
+ maxPixels=max_pixels,
+ **reducer_args,
+ )
-def image_regions(image, regions, reducer, scale, seriesProperty, xLabels, **kwargs):
- # TODO
- pass
+ histograms = {
+ band: histogram.get(band).getInfo() for band in image.bandNames().getInfo()
+ }
+
+ # Create bqplot histograms for each band.
+ def create_histogram(
+ hist_data: Dict[str, Any], color: str, label: str
+ ) -> bq.Figure:
+ """
+ Creates a bqplot histogram for the given histogram data.
+
+ Args:
+ hist_data (dict): The histogram data.
+ color (str): The color of the histogram.
+ label (str): The label of the histogram.
+
+ Returns:
+ bq.Figure: The bqplot figure for the histogram.
+ """
+ x_data = np.array(hist_data["bucketMeans"])
+ y_data = np.array(hist_data["histogram"])
+
+ x_sc = bq.LinearScale()
+ y_sc = bq.LinearScale()
+
+ bar = bq.Bars(
+ x=x_data,
+ y=y_data,
+ scales={"x": x_sc, "y": y_sc},
+ colors=[color],
+ display_legend=True,
+ labels=[label],
+ )
+ ax_x = bq.Axis(scale=x_sc, label="Reflectance (x1e4)", tick_format="0.0f")
+ ax_y = bq.Axis(
+ scale=y_sc, orientation="vertical", label="Count", tick_format="0.0f"
+ )
-def image_series(imageCollection, region, reducer, scale, xProperty, **kwargs):
- # TODO
- pass
+ return bq.Figure(marks=[bar], axes=[ax_x, ax_y])
+ # Define colors and labels for the bands.
+ band_colors = kwargs.get("colors", ["#cf513e", "#1d6b99", "#f0af07"])
+ band_labels = kwargs.get("labels", image.bandNames().getInfo())
-def image_seriesByRegion(
- imageCollection, regions, reducer, band, scale, xProperty, seriesProperty, **kwargs
-):
- # TODO
- pass
+ # Create and combine histograms for each band.
+ histograms_fig = []
+ for band, color, label in zip(histograms.keys(), band_colors, band_labels):
+ histograms_fig.append(create_histogram(histograms[band], color, label))
+
+ combined_fig = bq.Figure(
+ marks=[fig.marks[0] for fig in histograms_fig],
+ axes=histograms_fig[0].axes,
+ **kwargs,
+ )
+
+ for fig, label in zip(histograms_fig, band_labels):
+ fig.marks[0].labels = [label]
+
+ combined_fig.legend_location = kwargs.get("legend_location", "top-right")
+
+ return combined_fig
+
+
+def image_regions(
+ image: ee.Image,
+ regions: Union[ee.FeatureCollection, ee.Geometry],
+ reducer: Union[str, ee.Reducer],
+ scale: int,
+ series_property: str,
+ x_labels: List[str],
+ **kwargs: Any,
+) -> None:
+ """
+ Generates a Chart from an image by regions. Extracts and plots band values
+ in multiple regions.
+
+ Args:
+ image (ee.Image): Image to extract band values from.
+ regions (Union[ee.FeatureCollection, ee.Geometry]): Regions to reduce.
+ Defaults to the image's footprint.
+ reducer (Union[str, ee.Reducer]): The reducer type for zonal statistics.
+ Can be one of 'mean', 'median', 'sum', 'min', 'max', etc.
+ scale (int): The scale in meters at which to perform the analysis.
+ series_property (str): The property to use for labeling the series.
+ x_labels (List[str]): List of x-axis labels.
+ **kwargs: Additional keyword arguments.
+
+ Returns:
+ bq.Figure: The bqplot figure.
+ """
+ fc = zonal_stats(
+ image, regions, stat_type=reducer, scale=scale, verbose=False, return_fc=True
+ )
+ bands = image.bandNames().getInfo()
+ fc = fc.select(bands + [series_property])
+ return feature_by_property(fc, x_labels, series_property, **kwargs)
+
+
+def image_series(
+ image_collection: ee.ImageCollection,
+ region: Union[ee.Geometry, ee.FeatureCollection],
+ reducer: Optional[Union[str, ee.Reducer]] = None,
+ scale: Optional[int] = None,
+ x_property: str = "system:time_start",
+ chart_type: str = "LineChart",
+ x_cols: Optional[List[str]] = None,
+ y_cols: Optional[List[str]] = None,
+ colors: Optional[List[str]] = None,
+ title: Optional[str] = None,
+ x_label: Optional[str] = None,
+ y_label: Optional[str] = None,
+ **kwargs: Any,
+) -> Chart:
+ """
+ Generates a time series chart of an image collection for a specific region.
+
+ Args:
+ image_collection (ee.ImageCollection): The image collection to analyze.
+ region (Union[ee.Geometry, ee.FeatureCollection]): The region to reduce.
+ reducer (Optional[Union[str, ee.Reducer]]): The reducer to use.
+ scale (Optional[int]): The scale in meters at which to perform the analysis.
+ x_property (str): The name of the property to use as the x-axis values.
+ chart_type (str): The type of chart to create. Supported types are
+ 'ScatterChart', 'LineChart', 'ColumnChart', 'BarChart',
+ 'PieChart', 'AreaChart', and 'Table'.
+ x_cols (Optional[List[str]]): The columns to use for the x-axis.
+ Defaults to the first column.
+ y_cols (Optional[List[str]]): The columns to use for the y-axis.
+ Defaults to the second column.
+ colors (Optional[List[str]]): The colors to use for the chart.
+ Defaults to a predefined list of colors.
+ title (Optional[str]): The title of the chart. Defaults to the
+ chart type.
+ x_label (Optional[str]): The label for the x-axis. Defaults to an
+ empty string.
+ y_label (Optional[str]): The label for the y-axis. Defaults to an
+ empty string.
+ **kwargs: Additional keyword arguments to pass to the bqplot Figure
+ or mark objects. For axes_options, see
+ https://bqplot.github.io/bqplot/api/axes
+
+ Returns:
+ Chart: The chart object.
+ """
+
+ if reducer is None:
+ reducer = ee.Reducer.mean()
+
+ band_names = image_collection.first().bandNames().getInfo()
+
+ # Function to reduce the region and get the mean for each image.
+ def get_stats(image):
+ stats = image.reduceRegion(reducer=reducer, geometry=region, scale=scale)
+
+ results = {}
+ for band in band_names:
+ results[band] = stats.get(band)
+
+ if x_property == "system:time_start" or x_property == "system:time_end":
+ results["date"] = image.date().format("YYYY-MM-dd")
+ else:
+ results[x_property] = image.get(x_property).getInfo()
+
+ return ee.Feature(None, results)
+
+ # Apply the function over the image collection.
+ fc = ee.FeatureCollection(
+ image_collection.map(get_stats).filter(ee.Filter.notNull(band_names))
+ )
+ df = ee_to_df(fc)
+ if "date" in df.columns:
+ df["date"] = pd.to_datetime(df["date"])
+
+ fig = Chart(
+ df,
+ chart_type,
+ x_cols,
+ y_cols,
+ colors,
+ title,
+ x_label,
+ y_label,
+ **kwargs,
+ )
+ return fig
+
+
+def image_series_by_region(
+ image_collection: ee.ImageCollection,
+ regions: Union[ee.FeatureCollection, ee.Geometry],
+ reducer: Optional[Union[str, ee.Reducer]] = None,
+ band: Optional[str] = None,
+ scale: Optional[int] = None,
+ x_property: str = "system:time_start",
+ series_property: str = "system:index",
+ chart_type: str = "LineChart",
+ x_cols: Optional[List[str]] = None,
+ y_cols: Optional[List[str]] = None,
+ colors: Optional[List[str]] = None,
+ title: Optional[str] = None,
+ x_label: Optional[str] = None,
+ y_label: Optional[str] = None,
+ **kwargs: Any,
+) -> Chart:
+ """
+ Generates a time series chart of an image collection for multiple regions.
+
+ Args:
+ image_collection (ee.ImageCollection): The image collection to analyze.
+ regions (ee.FeatureCollection | ee.Geometry): The regions to reduce.
+ reducer (str | ee.Reducer): The reducer type for zonal statistics.
+ band (str): The name of the band to analyze.
+ scale (int): The scale in meters at which to perform the analysis.
+ x_property (str): The name of the property to use as the x-axis values.
+ series_property (str): The property to use for labeling the series.
+ chart_type (str): The type of chart to create. Supported types are
+ 'ScatterChart', 'LineChart', 'ColumnChart', 'BarChart',
+ 'PieChart', 'AreaChart', and 'Table'.
+ x_cols (Optional[List[str]]): The columns to use for the x-axis.
+ Defaults to the first column.
+ y_cols (Optional[List[str]]): The columns to use for the y-axis.
+ Defaults to the second column.
+ colors (Optional[List[str]]): The colors to use for the chart.
+ Defaults to a predefined list of colors.
+ title (Optional[str]): The title of the chart. Defaults to the
+ chart type.
+ x_label (Optional[str]): The label for the x-axis. Defaults to an
+ empty string.
+ y_label (Optional[str]): The label for the y-axis. Defaults to an
+ empty string.
+ **kwargs: Additional keyword arguments to pass to the bqplot Figure
+ or mark objects. For axes_options, see
+ https://bqplot.github.io/bqplot/api/axes
+
+ Returns:
+ Chart: The chart object.
+ """
+ if reducer is None:
+ reducer = ee.Reducer.mean()
+
+ if band is None:
+ band = image_collection.first().bandNames().get(0).getInfo()
+
+ image = image_collection.select(band).toBands()
+
+ fc = zonal_stats(
+ image, regions, stat_type=reducer, scale=scale, verbose=False, return_fc=True
+ )
+ columns = image.bandNames().getInfo() + [series_property]
+ df = ee_to_df(fc, columns=columns)
+
+ headers = df[series_property].tolist()
+ df = df.drop(columns=[series_property]).T
+ df.columns = headers
+
+ if x_property == "system:time_start" or x_property == "system:time_end":
+ indexes = image_dates(image_collection).getInfo()
+ df["index"] = pd.to_datetime(indexes)
+
+ else:
+ indexes = image_collection.aggregate_array(x_property).getInfo()
+ df["index"] = indexes
+
+ fig = Chart(
+ df,
+ chart_type,
+ x_cols,
+ y_cols,
+ colors,
+ title,
+ x_label,
+ y_label,
+ **kwargs,
+ )
+ return fig
+
+
+def array_values(
+ array: Union[ee.Array, ee.List, List[List[float]]],
+ x_labels: Optional[Union[ee.Array, ee.List, List[float]]] = None,
+ axis: int = 1,
+ series_names: Optional[List[str]] = None,
+ chart_type: str = "LineChart",
+ colors: Optional[List[str]] = None,
+ title: Optional[str] = None,
+ x_label: Optional[str] = None,
+ y_label: Optional[str] = None,
+ **kwargs: Any,
+) -> Chart:
+ """
+ Converts an array to a DataFrame and generates a chart.
+
+ Args:
+ array (Union[ee.Array, ee.List, List[List[float]]]): The array to convert.
+ x_labels (Optional[Union[ee.Array, ee.List, List[float]]]): The labels
+ for the x-axis. Defaults to None.
+ axis (int): The axis along which to transpose the array if needed. Defaults to 1.
+ series_names (Optional[List[str]]): The names of the series. Defaults to None.
+ chart_type (str): The type of chart to create. Defaults to "LineChart".
+ colors (Optional[List[str]]): The colors to use for the chart. Defaults to None.
+ title (Optional[str]): The title of the chart. Defaults to None.
+ x_label (Optional[str]): The label for the x-axis. Defaults to None.
+ y_label (Optional[str]): The label for the y-axis. Defaults to None.
+ **kwargs: Additional keyword arguments to pass to the Chart constructor.
+
+ Returns:
+ Chart: The generated chart.
+ """
+
+ df = array_to_df(array, x_values=x_labels, y_labels=series_names, axis=axis)
+ fig = Chart(
+ df,
+ x_cols=["x"],
+ y_cols=df.columns.tolist()[1:],
+ chart_type=chart_type,
+ colors=colors,
+ title=title,
+ x_label=x_label,
+ y_label=y_label,
+ **kwargs,
+ )
+ return fig
diff --git a/geemap/common.py b/geemap/common.py
index d0c0a0b4e2..84b5a7b7e4 100644
--- a/geemap/common.py
+++ b/geemap/common.py
@@ -16280,3 +16280,24 @@ def xarray_to_raster(dataset, filename: str, **kwargs: Dict[str, Any]) -> None:
dataset = dataset.rename(new_names)
dataset.transpose(..., "y", "x").rio.to_raster(filename, **kwargs)
+
+
+def hex_to_rgba(hex_color: str, opacity: float) -> str:
+ """
+ Converts a hex color code to an RGBA color string.
+
+ Args:
+ hex_color (str): The hex color code to convert. It can be in the format
+ '#RRGGBB' or 'RRGGBB'.
+ opacity (float): The opacity value for the RGBA color. It should be a
+ float between 0.0 (completely transparent) and 1.0 (completely opaque).
+
+ Returns:
+ str: The RGBA color string in the format 'rgba(R, G, B, A)'.
+ """
+ hex_color = hex_color.lstrip("#")
+ h_len = len(hex_color)
+ r, g, b = (
+ int(hex_color[i : i + h_len // 3], 16) for i in range(0, h_len, h_len // 3)
+ )
+ return f"rgba({r},{g},{b},{opacity})"
diff --git a/mkdocs.yml b/mkdocs.yml
index eb8149553e..d13ede44ae 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -77,7 +77,7 @@ extra:
- icon: fontawesome/brands/twitter
link: https://twitter.com/giswqs
- icon: fontawesome/brands/linkedin
- link: https://www.linkedin.com/in/qiushengwu
+ link: https://www.linkedin.com/in/giswqs
- icon: fontawesome/brands/youtube
link: https://youtube.com/@giswqs
analytics:
@@ -298,6 +298,11 @@ nav:
- notebooks/141_image_array_viz.ipynb
- notebooks/142_google_maps.ipynb
- notebooks/143_precipitation_timelapse.ipynb
+ - notebooks/144_chart_features.ipynb
+ - notebooks/145_chart_image.ipynb
+ - notebooks/146_chart_image_collection.ipynb
+ - notebooks/147_chart_array_list.ipynb
+ - notebooks/148_chart_data_table.ipynb
- notebooks/149_gemini.ipynb
# - miscellaneous:
# - notebooks/cartoee_colab.ipynb