From ae45627922bbec99c3ebdf2d9e0bdb1684af7034 Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Tue, 19 Dec 2023 16:11:40 -0500 Subject: [PATCH 01/27] Add image chart functions --- examples/notebooks/63_charts.ipynb | 1820 ++-------------------------- geemap/chart.py | 48 +- 2 files changed, 140 insertions(+), 1728 deletions(-) diff --git a/examples/notebooks/63_charts.ipynb b/examples/notebooks/63_charts.ipynb index cff5938b39..07208284ee 100644 --- a/examples/notebooks/63_charts.ipynb +++ b/examples/notebooks/63_charts.ipynb @@ -18,94 +18,14 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ + "import calendar\n", "import ee\n", "import geemap\n", - "import geemap.chart as chart\n", - "\n", - "# from geemap import chart" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# geemap.update_package()" + "import geemap.chart as chart" ] }, { @@ -119,214 +39,23 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "f66954a723f2433dbd376f43d391b26c", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Map(center=[0, 0], controls=(WidgetControl(options=['position', 'transparent_bg'], widget=SearchDataGUI(childr…" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ - "Map = geemap.Map()\n", - "\n", + "m = geemap.Map()\n", "features = ee.FeatureCollection('projects/google/charts_feature_example').select(\n", " '[0-9][0-9]_tmean|label'\n", ")\n", - "\n", - "Map.addLayer(features, {}, \"Ecoregions\")\n", - "Map" + "m.addLayer(features, {}, \"Ecoregions\")\n", + "m" ] }, { "cell_type": "code", - "execution_count": 19, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
01_tmean02_tmean03_tmean04_tmean05_tmean06_tmean07_tmean08_tmean09_tmean10_tmean11_tmean12_tmeanlabel
05.7910367.64501110.45465814.25164019.03281523.79050625.06657723.84525921.45479515.9979889.8493215.641386Desert
12.7924673.6090745.0329327.12013710.39537613.77613417.85017717.91998315.20657310.0817094.7847062.317886Forest
2-3.756608-1.9902202.5701467.72130613.64387519.03355822.75305921.84834616.4017709.2630212.021918-3.426706Grassland
\n", - "
" - ], - "text/plain": [ - " 01_tmean 02_tmean 03_tmean 04_tmean 05_tmean 06_tmean 07_tmean \\\n", - "0 5.791036 7.645011 10.454658 14.251640 19.032815 23.790506 25.066577 \n", - "1 2.792467 3.609074 5.032932 7.120137 10.395376 13.776134 17.850177 \n", - "2 -3.756608 -1.990220 2.570146 7.721306 13.643875 19.033558 22.753059 \n", - "\n", - " 08_tmean 09_tmean 10_tmean 11_tmean 12_tmean label \n", - "0 23.845259 21.454795 15.997988 9.849321 5.641386 Desert \n", - "1 17.919983 15.206573 10.081709 4.784706 2.317886 Forest \n", - "2 21.848346 16.401770 9.263021 2.021918 -3.426706 Grassland " - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "df = geemap.ee_to_df(features)\n", "df" @@ -334,76 +63,13 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "xProperty = \"label\"\n", "yProperties = [str(x).zfill(2) + \"_tmean\" for x in range(1, 13)]\n", - "\n", - "labels = [\n", - " 'Jan',\n", - " 'Feb',\n", - " 'Mar',\n", - " 'Apr',\n", - " 'May',\n", - " 'Jun',\n", - " 'Jul',\n", - " 'Aug',\n", - " 'Sep',\n", - " 'Oct',\n", - " 'Nov',\n", - " 'Dec',\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", + "labels = list(calendar.month_abbr)[1:] # a list of month labels, e.g. ['Jan', 'Feb', ...]\n", "title = \"Average Monthly Temperature by Ecoregion\"\n", "xlabel = \"Ecoregion\"\n", "ylabel = \"Temperature\"" @@ -411,110 +77,26 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "options = {\n", " \"labels\": labels,\n", - " \"colors\": colors,\n", " \"title\": title,\n", " \"xlabel\": xlabel,\n", " \"ylabel\": ylabel,\n", " \"legend_location\": \"top-left\",\n", " \"height\": \"500px\",\n", + " # \"colors\": ['red', 'green', 'blue'],\n", "}" ] }, { "cell_type": "code", - "execution_count": 22, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "42549b47340944a4a59a2bda020fd30d", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "VBox(children=(Figure(axes=[Axis(label='Ecoregion', scale=OrdinalScale()), Axis(label='Temperature', orientati…" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "chart.feature_byFeature(features, xProperty, yProperties, **options)" ] @@ -526,785 +108,6 @@ "![](https://i.imgur.com/9xzsUxg.png)" ] }, - { - "cell_type": "code", - "execution_count": 26, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "ecoregions = ee.FeatureCollection('projects/google/charts_feature_example')\n", - "normClim = ee.ImageCollection('OREGONSTATE/PRISM/Norm81m').toBands().select('[0-9][0-9]_tmean')" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
01_ppt01_tdmean01_tmax01_tmean01_tmin01_vpdmax01_vpdmin02_ppt02_tdmean02_tmax...12_ppt12_tdmean12_tmax12_tmean12_tmin12_vpdmax12_vpdminlabelvaluewarm
027.954341-5.93898114.0518545.791036-2.46978212.4875921.35483821.858469-5.51650816.323491...35.557367-5.75037513.8434685.641386-2.56069712.1732141.272226Desert01
1235.373540-0.6960756.5478292.792467-0.9628964.0987990.508449181.531780-0.7035668.196231...273.243438-0.6680415.8270722.317886-1.1913013.5216960.480811Forest11
29.636893-9.2439952.920941-3.756608-10.4341574.7509010.48778513.915104-7.8654284.853363...11.198115-8.8636833.177410-3.426706-10.0308225.0383560.488113Grassland20
\n", - "

3 rows × 87 columns

\n", - "
" - ], - "text/plain": [ - " 01_ppt 01_tdmean 01_tmax 01_tmean 01_tmin 01_vpdmax \\\n", - "0 27.954341 -5.938981 14.051854 5.791036 -2.469782 12.487592 \n", - "1 235.373540 -0.696075 6.547829 2.792467 -0.962896 4.098799 \n", - "2 9.636893 -9.243995 2.920941 -3.756608 -10.434157 4.750901 \n", - "\n", - " 01_vpdmin 02_ppt 02_tdmean 02_tmax ... 12_ppt 12_tdmean \\\n", - "0 1.354838 21.858469 -5.516508 16.323491 ... 35.557367 -5.750375 \n", - "1 0.508449 181.531780 -0.703566 8.196231 ... 273.243438 -0.668041 \n", - "2 0.487785 13.915104 -7.865428 4.853363 ... 11.198115 -8.863683 \n", - "\n", - " 12_tmax 12_tmean 12_tmin 12_vpdmax 12_vpdmin label value \\\n", - "0 13.843468 5.641386 -2.560697 12.173214 1.272226 Desert 0 \n", - "1 5.827072 2.317886 -1.191301 3.521696 0.480811 Forest 1 \n", - "2 3.177410 -3.426706 -10.030822 5.038356 0.488113 Grassland 2 \n", - "\n", - " warm \n", - "0 1 \n", - "1 1 \n", - "2 0 \n", - "\n", - "[3 rows x 87 columns]" - ] - }, - "execution_count": 24, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "geemap.ee_to_df(ecoregions)" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
    • type:Image
        • id:01_tmean
        • crs:GEOGCS[\"NAD83\", \n", - " DATUM[\"North_American_Datum_1983\", \n", - " SPHEROID[\"GRS_1980\", 6378137.0, 298.257222101]], \n", - " PRIMEM[\"Greenwich\", 0.0], \n", - " UNIT[\"degree\", 0.017453292519943295], \n", - " AXIS[\"Longitude\", EAST], \n", - " AXIS[\"Latitude\", NORTH]]
          • 0:0.041666666667
          • 1:0
          • 2:-125.0208333333335
          • 3:0
          • 4:-0.041666666667
          • 5:49.9375000000025
          • type:PixelType
          • precision:float
          • 0:1405
          • 1:621
        • id:02_tmean
        • crs:GEOGCS[\"NAD83\", \n", - " DATUM[\"North_American_Datum_1983\", \n", - " SPHEROID[\"GRS_1980\", 6378137.0, 298.257222101]], \n", - " PRIMEM[\"Greenwich\", 0.0], \n", - " UNIT[\"degree\", 0.017453292519943295], \n", - " AXIS[\"Longitude\", EAST], \n", - " AXIS[\"Latitude\", NORTH]]
          • 0:0.041666666667
          • 1:0
          • 2:-125.0208333333335
          • 3:0
          • 4:-0.041666666667
          • 5:49.9375000000025
          • type:PixelType
          • precision:float
          • 0:1405
          • 1:621
        • id:03_tmean
        • crs:GEOGCS[\"NAD83\", \n", - " DATUM[\"North_American_Datum_1983\", \n", - " SPHEROID[\"GRS_1980\", 6378137.0, 298.257222101]], \n", - " PRIMEM[\"Greenwich\", 0.0], \n", - " UNIT[\"degree\", 0.017453292519943295], \n", - " AXIS[\"Longitude\", EAST], \n", - " AXIS[\"Latitude\", NORTH]]
          • 0:0.041666666667
          • 1:0
          • 2:-125.0208333333335
          • 3:0
          • 4:-0.041666666667
          • 5:49.9375000000025
          • type:PixelType
          • precision:float
          • 0:1405
          • 1:621
        • id:04_tmean
        • crs:GEOGCS[\"NAD83\", \n", - " DATUM[\"North_American_Datum_1983\", \n", - " SPHEROID[\"GRS_1980\", 6378137.0, 298.257222101]], \n", - " PRIMEM[\"Greenwich\", 0.0], \n", - " UNIT[\"degree\", 0.017453292519943295], \n", - " AXIS[\"Longitude\", EAST], \n", - " AXIS[\"Latitude\", NORTH]]
          • 0:0.041666666667
          • 1:0
          • 2:-125.0208333333335
          • 3:0
          • 4:-0.041666666667
          • 5:49.9375000000025
          • type:PixelType
          • precision:float
          • 0:1405
          • 1:621
        • id:05_tmean
        • crs:GEOGCS[\"NAD83\", \n", - " DATUM[\"North_American_Datum_1983\", \n", - " SPHEROID[\"GRS_1980\", 6378137.0, 298.257222101]], \n", - " PRIMEM[\"Greenwich\", 0.0], \n", - " UNIT[\"degree\", 0.017453292519943295], \n", - " AXIS[\"Longitude\", EAST], \n", - " AXIS[\"Latitude\", NORTH]]
          • 0:0.041666666667
          • 1:0
          • 2:-125.0208333333335
          • 3:0
          • 4:-0.041666666667
          • 5:49.9375000000025
          • type:PixelType
          • precision:float
          • 0:1405
          • 1:621
        • id:06_tmean
        • crs:GEOGCS[\"NAD83\", \n", - " DATUM[\"North_American_Datum_1983\", \n", - " SPHEROID[\"GRS_1980\", 6378137.0, 298.257222101]], \n", - " PRIMEM[\"Greenwich\", 0.0], \n", - " UNIT[\"degree\", 0.017453292519943295], \n", - " AXIS[\"Longitude\", EAST], \n", - " AXIS[\"Latitude\", NORTH]]
          • 0:0.041666666667
          • 1:0
          • 2:-125.0208333333335
          • 3:0
          • 4:-0.041666666667
          • 5:49.9375000000025
          • type:PixelType
          • precision:float
          • 0:1405
          • 1:621
        • id:07_tmean
        • crs:GEOGCS[\"NAD83\", \n", - " DATUM[\"North_American_Datum_1983\", \n", - " SPHEROID[\"GRS_1980\", 6378137.0, 298.257222101]], \n", - " PRIMEM[\"Greenwich\", 0.0], \n", - " UNIT[\"degree\", 0.017453292519943295], \n", - " AXIS[\"Longitude\", EAST], \n", - " AXIS[\"Latitude\", NORTH]]
          • 0:0.041666666667
          • 1:0
          • 2:-125.0208333333335
          • 3:0
          • 4:-0.041666666667
          • 5:49.9375000000025
          • type:PixelType
          • precision:float
          • 0:1405
          • 1:621
        • id:08_tmean
        • crs:GEOGCS[\"NAD83\", \n", - " DATUM[\"North_American_Datum_1983\", \n", - " SPHEROID[\"GRS_1980\", 6378137.0, 298.257222101]], \n", - " PRIMEM[\"Greenwich\", 0.0], \n", - " UNIT[\"degree\", 0.017453292519943295], \n", - " AXIS[\"Longitude\", EAST], \n", - " AXIS[\"Latitude\", NORTH]]
          • 0:0.041666666667
          • 1:0
          • 2:-125.0208333333335
          • 3:0
          • 4:-0.041666666667
          • 5:49.9375000000025
          • type:PixelType
          • precision:float
          • 0:1405
          • 1:621
        • id:09_tmean
        • crs:GEOGCS[\"NAD83\", \n", - " DATUM[\"North_American_Datum_1983\", \n", - " SPHEROID[\"GRS_1980\", 6378137.0, 298.257222101]], \n", - " PRIMEM[\"Greenwich\", 0.0], \n", - " UNIT[\"degree\", 0.017453292519943295], \n", - " AXIS[\"Longitude\", EAST], \n", - " AXIS[\"Latitude\", NORTH]]
          • 0:0.041666666667
          • 1:0
          • 2:-125.0208333333335
          • 3:0
          • 4:-0.041666666667
          • 5:49.9375000000025
          • type:PixelType
          • precision:float
          • 0:1405
          • 1:621
        • id:10_tmean
        • crs:GEOGCS[\"NAD83\", \n", - " DATUM[\"North_American_Datum_1983\", \n", - " SPHEROID[\"GRS_1980\", 6378137.0, 298.257222101]], \n", - " PRIMEM[\"Greenwich\", 0.0], \n", - " UNIT[\"degree\", 0.017453292519943295], \n", - " AXIS[\"Longitude\", EAST], \n", - " AXIS[\"Latitude\", NORTH]]
          • 0:0.041666666667
          • 1:0
          • 2:-125.0208333333335
          • 3:0
          • 4:-0.041666666667
          • 5:49.9375000000025
          • type:PixelType
          • precision:float
          • 0:1405
          • 1:621
        • id:11_tmean
        • crs:GEOGCS[\"NAD83\", \n", - " DATUM[\"North_American_Datum_1983\", \n", - " SPHEROID[\"GRS_1980\", 6378137.0, 298.257222101]], \n", - " PRIMEM[\"Greenwich\", 0.0], \n", - " UNIT[\"degree\", 0.017453292519943295], \n", - " AXIS[\"Longitude\", EAST], \n", - " AXIS[\"Latitude\", NORTH]]
          • 0:0.041666666667
          • 1:0
          • 2:-125.0208333333335
          • 3:0
          • 4:-0.041666666667
          • 5:49.9375000000025
          • type:PixelType
          • precision:float
          • 0:1405
          • 1:621
        • id:12_tmean
        • crs:GEOGCS[\"NAD83\", \n", - " DATUM[\"North_American_Datum_1983\", \n", - " SPHEROID[\"GRS_1980\", 6378137.0, 298.257222101]], \n", - " PRIMEM[\"Greenwich\", 0.0], \n", - " UNIT[\"degree\", 0.017453292519943295], \n", - " AXIS[\"Longitude\", EAST], \n", - " AXIS[\"Latitude\", NORTH]]
          • 0:0.041666666667
          • 1:0
          • 2:-125.0208333333335
          • 3:0
          • 4:-0.041666666667
          • 5:49.9375000000025
          • type:PixelType
          • precision:float
          • 0:1405
          • 1:621
" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 27, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "normClim" - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "fc = geemap.zonal_stats(normClim, ecoregions, stat_type='MEAN', scale=500, return_fc=True, verbose=False)" - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
01_ppt01_tdmean01_tmax01_tmean01_tmin01_vpdmax01_vpdmin02_ppt02_tdmean02_tmax...12_ppt12_tdmean12_tmax12_tmean12_tmin12_vpdmax12_vpdminlabelvaluewarm
027.954341-5.93898114.0518545.910990-2.46978212.4875921.35483821.858469-5.51650816.323491...35.557367-5.75037513.8434685.783772-2.56069712.1732141.272226Desert01
1235.373540-0.6960756.5478293.094200-0.9628964.0987990.508449181.531780-0.7035668.196231...273.243438-0.6680415.8270722.610459-1.1913013.5216960.480811Forest11
29.636893-9.2439952.920941-3.734058-10.4341574.7509010.48778513.915104-7.8654284.853363...11.198115-8.8636833.177410-3.396242-10.0308225.0383560.488113Grassland20
\n", - "

3 rows × 87 columns

\n", - "
" - ], - "text/plain": [ - " 01_ppt 01_tdmean 01_tmax 01_tmean 01_tmin 01_vpdmax \\\n", - "0 27.954341 -5.938981 14.051854 5.910990 -2.469782 12.487592 \n", - "1 235.373540 -0.696075 6.547829 3.094200 -0.962896 4.098799 \n", - "2 9.636893 -9.243995 2.920941 -3.734058 -10.434157 4.750901 \n", - "\n", - " 01_vpdmin 02_ppt 02_tdmean 02_tmax ... 12_ppt 12_tdmean \\\n", - "0 1.354838 21.858469 -5.516508 16.323491 ... 35.557367 -5.750375 \n", - "1 0.508449 181.531780 -0.703566 8.196231 ... 273.243438 -0.668041 \n", - "2 0.487785 13.915104 -7.865428 4.853363 ... 11.198115 -8.863683 \n", - "\n", - " 12_tmax 12_tmean 12_tmin 12_vpdmax 12_vpdmin label value \\\n", - "0 13.843468 5.783772 -2.560697 12.173214 1.272226 Desert 0 \n", - "1 5.827072 2.610459 -1.191301 3.521696 0.480811 Forest 1 \n", - "2 3.177410 -3.396242 -10.030822 5.038356 0.488113 Grassland 2 \n", - "\n", - " warm \n", - "0 1 \n", - "1 1 \n", - "2 0 \n", - "\n", - "[3 rows x 87 columns]" - ] - }, - "execution_count": 32, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "geemap.ee_to_df(fc)" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -1316,219 +119,20 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "be0a3e6aa46e441eaa345ad6ea770d36", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Map(center=[0, 0], controls=(WidgetControl(options=['position', 'transparent_bg'], widget=SearchDataGUI(childr…" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ - "Map = geemap.Map()\n", - "\n", "features = ee.FeatureCollection('projects/google/charts_feature_example').select(\n", " '[0-9][0-9]_ppt|label'\n", - ")\n", - "\n", - "Map.addLayer(features, {}, 'Features')\n", - "Map" + ")" ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
01_ppt02_ppt03_ppt04_ppt05_ppt06_ppt07_ppt08_ppt09_ppt10_ppt11_ppt12_pptlabel
027.95434121.85846917.5791248.2525438.37221614.80212380.38940979.32688637.41224732.72869422.80909935.557367Desert
1235.373540181.531780181.917962150.759019112.39213972.70987723.40921625.39556654.064534121.976155261.008798273.243438Forest
29.63689313.91510429.76129557.73611284.27645086.13686076.18175560.18963045.83316138.13490720.08592311.198115Grassland
\n", - "
" - ], - "text/plain": [ - " 01_ppt 02_ppt 03_ppt 04_ppt 05_ppt 06_ppt \\\n", - "0 27.954341 21.858469 17.579124 8.252543 8.372216 14.802123 \n", - "1 235.373540 181.531780 181.917962 150.759019 112.392139 72.709877 \n", - "2 9.636893 13.915104 29.761295 57.736112 84.276450 86.136860 \n", - "\n", - " 07_ppt 08_ppt 09_ppt 10_ppt 11_ppt 12_ppt \\\n", - "0 80.389409 79.326886 37.412247 32.728694 22.809099 35.557367 \n", - "1 23.409216 25.395566 54.064534 121.976155 261.008798 273.243438 \n", - "2 76.181755 60.189630 45.833161 38.134907 20.085923 11.198115 \n", - "\n", - " label \n", - "0 Desert \n", - "1 Forest \n", - "2 Grassland " - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "df = geemap.ee_to_df(features)\n", "df" @@ -1536,102 +140,19 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "keys = [str(x).zfill(2) + \"_ppt\" for x in range(1, 13)]\n", - "values = [\n", - " 'Jan',\n", - " 'Feb',\n", - " 'Mar',\n", - " 'Apr',\n", - " 'May',\n", - " 'Jun',\n", - " 'Jul',\n", - " 'Aug',\n", - " 'Sep',\n", - " 'Oct',\n", - " 'Nov',\n", - " 'Dec',\n", - "]" + "values = list(calendar.month_abbr)[1:] # a list of month labels, e.g. ['Jan', 'Feb', ...]" ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "xProperties = dict(zip(keys, values))\n", "seriesProperty = \"label\"" @@ -1639,44 +160,9 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "options = {\n", " 'title': \"Average Ecoregion Precipitation by Month\",\n", @@ -1690,58 +176,9 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "2ad49b761e9e413e8e1cdaf59294a9d7", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "VBox(children=(Figure(axes=[Axis(label='Month', scale=OrdinalScale()), Axis(label='Precipitation (mm)', orient…" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "chart.feature_byProperty(features, xProperties, seriesProperty, **options)" ] @@ -1763,47 +200,10 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ - "Map = geemap.Map()\n", - "\n", "features = ee.FeatureCollection('projects/google/charts_feature_example')\n", "xProperty = 'label'\n", "yProperty = '01_tmean'\n", @@ -1812,44 +212,9 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "options = {\n", " 'title': \"Average January Temperature by Ecoregion\",\n", @@ -1864,61 +229,70 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "5455a0ff48e6453ca24e1738a3eeee5f", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "VBox(children=(Figure(axes=[Axis(label='Ecoregion', scale=OrdinalScale()), Axis(label='Jan temp (C)', orientat…" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "chart.feature_groups(features, xProperty, yProperty, seriesProperty, **options)" ] + }, + { + "cell_type": "markdown", + "id": "bfd431a7", + "metadata": {}, + "source": [ + "## Image Charts\n", + "\n", + "Reference: https://developers.google.com/earth-engine/guides/charts_image" + ] + }, + { + "cell_type": "markdown", + "id": "3c8c9d5e", + "metadata": {}, + "source": [ + "### Image chart by regions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bde5d45b", + "metadata": {}, + "outputs": [], + "source": [ + "ecoregions = ee.FeatureCollection('projects/google/charts_feature_example')\n", + "normClim = ee.ImageCollection('OREGONSTATE/PRISM/Norm81m').toBands().select('[0-9][0-9]_tmean')\n", + "labels = list(calendar.month_abbr)[1:] # a list of month labels, e.g. ['Jan', 'Feb', ...]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "035a392b", + "metadata": {}, + "outputs": [], + "source": [ + "chart.image_byRegion(\n", + " image=normClim, \n", + " regions=ecoregions, \n", + " reducer=\"mean\", \n", + " scale=500, \n", + " xProperty='label', \n", + " xlabel='Ecoregion',\n", + " ylabel='Temperature',\n", + " labels=labels,\n", + " # colors=['red', 'green', 'blue'],\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "abac630a", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/zLSEJLK.png)" + ] } ], "metadata": { diff --git a/geemap/chart.py b/geemap/chart.py index 1d2e8073c3..db86794c44 100644 --- a/geemap/chart.py +++ b/geemap/chart.py @@ -33,9 +33,26 @@ def __init__(self, features, default_labels, name, **kwargs): 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): @@ -101,8 +118,10 @@ def plot_chart(self): self.generate_tooltip() plt.ylim(*self.get_ylim()) - plt.xlabel(self.xlabel) - plt.ylabel(self.ylabel) + if self.xlabel: + plt.xlabel(self.xlabel) + if self.ylabel: + plt.ylabel(self.ylabel) if self.width: fig.layout.width = self.width @@ -441,8 +460,27 @@ def image_byClass( def image_byRegion(image, regions, reducer, scale, xProperty, **kwargs): - # TODO - pass + """ + 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. + xProperty (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_byFeature` 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 + [xProperty]] + feature_byFeature(df, xProperty, bands, **kwargs) def image_doySeries( From 325e8e5dc0330c520f866f0f24bd9a42850e2c3f Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Fri, 22 Dec 2023 14:52:29 -0500 Subject: [PATCH 02/27] Add LineChart class --- geemap/chart.py | 69 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/geemap/chart.py b/geemap/chart.py index db86794c44..22f07a57b9 100644 --- a/geemap/chart.py +++ b/geemap/chart.py @@ -134,6 +134,39 @@ 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, labels, name="line.chart", **kwargs): + super().__init__(features, labels, name, **kwargs) + + def plot_chart(self): + 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.xlabel: + plt.xlabel(self.xlabel) + if self.ylabel: + plt.ylabel(self.ylabel) + + 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.""" @@ -217,6 +250,42 @@ def get_data(self, xProperty, new_column_names): return x_data, y_data +class Image_byClass(LineChart): + """A object to define variables and get_data method.""" + + def __init__( + self, + image, + region, + reducer, + scale, + classLabels, + xLabels, + xProperty, + name="image.byClass", + **kwargs, + ): + self.classLabels = classLabels + self.xLabels = xLabels + super().__init__(image, classLabels, name, **kwargs) + self.x_data, self.y_data = self.get_data( + image, region, xProperty, reducer, scale + ) + + def get_data(self, image, region, xProperty, reducer, scale): + 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 + [xProperty]] + columns = df.columns.tolist() + columns.remove(xProperty) + x_data = columns + y_data = df.drop([xProperty], axis=1).to_numpy() + + return x_data, y_data + + def feature_byFeature( features: ee.FeatureCollection, xProperty: str, yProperties: list, **kwargs ): From a40c217181b7d6b1423b63bcf6fcca41796c3ba1 Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Sat, 25 May 2024 09:50:59 -0400 Subject: [PATCH 03/27] Add DataTable class --- geemap/chart.py | 182 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 180 insertions(+), 2 deletions(-) diff --git a/geemap/chart.py b/geemap/chart.py index 22f07a57b9..d98b40e9ae 100644 --- a/geemap/chart.py +++ b/geemap/chart.py @@ -9,12 +9,190 @@ 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 -from typing import Union +from typing import List, Optional, Union, Dict + + +class DataTable: + """ + A class to create and display various types of charts from data. + + Attributes: + df (pd.DataFrame): The data to be displayed in the charts. + chart: The bqplot Figure object for the chart. + """ + + def __init__(self, data: Union[Dict, pd.DataFrame], **kwargs): + """ + Initializes the DataTable with data. + + Args: + data (dict or pd.DataFrame): The input data. If it's a dictionary, it will be converted to a DataFrame. + **kwargs: Additional keyword arguments to pass to the pd.DataFrame constructor. + """ + if isinstance(data, pd.DataFrame): + self.df = data + else: + self.df = pd.DataFrame(data, **kwargs) + self.chart = None + + def setChartType( + self, + chart_type: str, + x_cols: Optional[List[str]] = None, + y_cols: Optional[List[str]] = None, + colors: Optional[List[str]] = None, + x_label: Optional[str] = None, + y_label: Optional[str] = None, + title: Optional[str] = None, + **kwargs, + ) -> "DataTable": + """ + 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'. + x_cols (list of str, optional): The columns to use for the x-axis. Defaults to the first column. + y_cols (list of str, optional): The columns to use for the y-axis. Defaults to the second column. + colors (list of str, optional): The colors to use for the chart. Defaults to a predefined list of colors. + x_label (str, optional): The label for the x-axis. Defaults to None. + y_label (str, optional): The label for the y-axis. Defaults to None. + title (str, optional): The title of the chart. Defaults to the chart type. + **kwargs: Additional keyword arguments to pass to the bqplot Figure or mark objects. + + Returns: + DataTable: The DataTable instance with the chart set. + """ + if x_cols is None: + x_cols = [self.df.columns[0]] + if y_cols is None: + y_cols = [self.df.columns[1]] + if title is None: + title = chart_type + + 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 + + x_sc = bq.OrdinalScale() + y_sc = bq.LinearScale() + + marks = [] + for i, (x_col, y_col) in enumerate(zip(x_cols, y_cols)): + color = colors[ + i % len(colors) + ] # Cycle through colors if not enough are provided + if chart_type == "ScatterChart": + marks.append( + bq.Scatter( + x=self.df[x_col], + y=self.df[y_col], + scales={"x": x_sc, "y": y_sc}, + colors=[color], + **kwargs, + ) + ) + elif chart_type == "LineChart": + marks.append( + bq.Lines( + x=self.df[x_col], + y=self.df[y_col], + scales={"x": x_sc, "y": y_sc}, + colors=[color], + **kwargs, + ) + ) + elif chart_type == "ColumnChart": + marks.append( + bq.Bars( + x=self.df[x_col], + y=self.df[y_col], + scales={"x": x_sc, "y": y_sc}, + colors=[color], + **kwargs, + ) + ) + elif chart_type == "BarChart": + marks.append( + bq.Bars( + x=self.df[x_col], + y=self.df[y_col], + scales={"x": x_sc, "y": y_sc}, + orientation="horizontal", + colors=[color], + **kwargs, + ) + ) + elif chart_type == "AreaChart": + marks.append( + bq.Lines( + x=self.df[x_col], + y=self.df[y_col], + scales={"x": x_sc, "y": y_sc}, + fill="bottom", + colors=[color], + **kwargs, + ) + ) + elif chart_type == "PieChart": + # Pie chart does not support multiple series in the same way; use only the first pair of x_col and y_col + self.chart = bq.Figure(title=title, **kwargs) + pie = bq.Pie( + sizes=self.df[y_cols[0]].tolist(), + labels=self.df[x_cols[0]].tolist(), + colors=colors[: len(self.df[x_cols[0]])], + **kwargs, + ) + self.chart.marks = [pie] + return self + elif chart_type == "Table": + self.chart = widgets.Output(**kwargs) + with self.chart: + display(self.df) + self.chart.layout = widgets.Layout(width="50%") + return self + else: + raise ValueError("Unsupported chart type") + + self.chart = bq.Figure(marks=marks, title=title, **kwargs) + x_axis = bq.Axis(scale=x_sc, label=x_label) + y_axis = bq.Axis(scale=y_sc, orientation="vertical", label=y_label) + self.chart.axes = [x_axis, y_axis] + + return self + + def display(self) -> None: + """ + Displays the chart. + """ + display(self.chart) class BaseChartClass: From 57fa6d8a573df1b9207e4c3948167cdcbf86ec67 Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Sat, 25 May 2024 19:47:42 -0400 Subject: [PATCH 04/27] Add BaseChart class --- geemap/chart.py | 122 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 97 insertions(+), 25 deletions(-) diff --git a/geemap/chart.py b/geemap/chart.py index d98b40e9ae..42bc6f7689 100644 --- a/geemap/chart.py +++ b/geemap/chart.py @@ -16,10 +16,32 @@ from IPython.display import display from .common import ee_to_df, zonal_stats -from typing import List, Optional, Union, Dict +from typing import List, Optional, Union, Dict, Any -class DataTable: +class DataTable(pd.DataFrame): + # To ensure compatibility with pandas methods that return DataFrames + _metadata = ["_name"] + + def __init__( + self, + data: Union[Dict[str, List[Any]], pd.DataFrame, None] = 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. + **kwargs: Additional keyword arguments to pass to the pd.DataFrame + constructor. + """ + self._name = kwargs.pop("name", "DataTable") + super().__init__(data, **kwargs) + + +class BaseChart: """ A class to create and display various types of charts from data. @@ -28,19 +50,19 @@ class DataTable: chart: The bqplot Figure object for the chart. """ - def __init__(self, data: Union[Dict, pd.DataFrame], **kwargs): + def __init__(self, data: Union[Dict, pd.DataFrame], **kwargs: Any) -> None: """ - Initializes the DataTable with data. + Initializes the BaseChart with data. Args: - data (dict or pd.DataFrame): The input data. If it's a dictionary, it will be converted to a DataFrame. - **kwargs: Additional keyword arguments to pass to the pd.DataFrame constructor. + data (dict or pd.DataFrame): The input data. If it's a dictionary, + it will be converted to a DataFrame. + **kwargs: Additional keyword arguments to pass to the pd.DataFrame + constructor. """ - if isinstance(data, pd.DataFrame): - self.df = data - else: - self.df = pd.DataFrame(data, **kwargs) + self.df = DataTable(data) self.chart = None + self.chart_type = None def setChartType( self, @@ -48,27 +70,38 @@ def setChartType( x_cols: Optional[List[str]] = None, y_cols: Optional[List[str]] = None, colors: Optional[List[str]] = None, - x_label: Optional[str] = None, - y_label: Optional[str] = None, + x_label: Optional[str] = "", + y_label: Optional[str] = "", title: Optional[str] = None, - **kwargs, - ) -> "DataTable": + **kwargs: Any, + ) -> "BaseChart": """ 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'. - x_cols (list of str, optional): The columns to use for the x-axis. Defaults to the first column. - y_cols (list of str, optional): The columns to use for the y-axis. Defaults to the second column. - colors (list of str, optional): The colors to use for the chart. Defaults to a predefined list of colors. - x_label (str, optional): The label for the x-axis. Defaults to None. - y_label (str, optional): The label for the y-axis. Defaults to None. - title (str, optional): The title of the chart. Defaults to the chart type. - **kwargs: Additional keyword arguments to pass to the bqplot Figure or mark objects. + chart_type (str): The type of chart to create. Supported types are + 'ScatterChart', 'LineChart', 'ColumnChart', 'BarChart', + 'PieChart', 'AreaChart', and 'Table'. + x_cols (list of str, optional): The columns to use for the x-axis. + Defaults to the first column. + y_cols (list of str, optional): The columns to use for the y-axis. + Defaults to the second column. + colors (list of str, optional): The colors to use for the chart. + Defaults to a predefined list of colors. + x_label (str, optional): The label for the x-axis. Defaults to an + empty string. + y_label (str, optional): The label for the y-axis. Defaults to an + empty string. + title (str, optional): The title of the chart. Defaults to the + chart type. + **kwargs: Additional keyword arguments to pass to the bqplot Figure + or mark objects. Returns: - DataTable: The DataTable instance with the chart set. + BaseChart: The BaseChart instance with the chart set. """ + self.chart_type = chart_type + if x_cols is None: x_cols = [self.df.columns[0]] if y_cols is None: @@ -140,23 +173,25 @@ def setChartType( ) ) elif chart_type == "BarChart": + if "orientation" not in kwargs: + kwargs["orientation"] = "horizontal" marks.append( bq.Bars( x=self.df[x_col], y=self.df[y_col], scales={"x": x_sc, "y": y_sc}, - orientation="horizontal", colors=[color], **kwargs, ) ) elif chart_type == "AreaChart": + if "fill" not in kwargs: + kwargs["fill"] = "bottom" marks.append( bq.Lines( x=self.df[x_col], y=self.df[y_col], scales={"x": x_sc, "y": y_sc}, - fill="bottom", colors=[color], **kwargs, ) @@ -188,6 +223,43 @@ def setChartType( return self + def getChartType(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 getDataTable(self) -> DataTable: + """ + Get the DataTable used by the chart. + + Returns: + DataTable: The DataTable instance containing the chart data. + """ + return self.df + + def setDataTable(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.df = DataTable(data) + + def setOptions(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.chart, key, value) + def display(self) -> None: """ Displays the chart. From ce2f0bf7953f746535070a68795e7eb03d669ac0 Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Sun, 26 May 2024 00:42:53 -0400 Subject: [PATCH 05/27] Add image_byClass function --- geemap/chart.py | 304 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 283 insertions(+), 21 deletions(-) diff --git a/geemap/chart.py b/geemap/chart.py index 42bc6f7689..ac0ddae2e1 100644 --- a/geemap/chart.py +++ b/geemap/chart.py @@ -106,8 +106,18 @@ def setChartType( x_cols = [self.df.columns[0]] if y_cols is None: y_cols = [self.df.columns[1]] - if title is None: - title = chart_type + + 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 title is not None: + kwargs["title"] = title if chart_type == "PieChart": if colors is None: @@ -142,6 +152,12 @@ def setChartType( color = colors[ i % len(colors) ] # Cycle through colors if not enough are provided + if "display_legend" not in kwargs and len(y_cols) > 1: + kwargs["display_legend"] = True + kwargs["labels"] = [y_col] + else: + kwargs["labels"] = [y_col] + if chart_type == "ScatterChart": marks.append( bq.Scatter( @@ -216,10 +232,9 @@ def setChartType( else: raise ValueError("Unsupported chart type") - self.chart = bq.Figure(marks=marks, title=title, **kwargs) x_axis = bq.Axis(scale=x_sc, label=x_label) y_axis = bq.Axis(scale=y_sc, orientation="vertical", label=y_label) - self.chart.axes = [x_axis, y_axis] + self.chart = bq.Figure(marks=marks, axes=[x_axis, y_axis], **kwargs) return self @@ -772,10 +787,65 @@ def grow_bin(bin_size, ref): def image_byClass( - image, classBand, region, reducer, scale, classLabels, xLabels, **kwargs + image, + classBand, + region, + reducer="MEAN", + scale=None, + classLabels=None, + xLabels=None, + chart_type="LineChart", + **kwargs, ): - # TODO - pass + """ + 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. + classBand (str): The band name to use as class labels. + region (ee.Geometry | ee.FeatureCollection): The region(s) to reduce. + 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. + classLabels (list): List of class labels. + xLabels (list): List of x-axis labels. + chart_type (str): The type of chart to create. Supported types are + 'ScatterChart', 'LineChart', 'ColumnChart', 'BarChart', 'PieChart', + 'AreaChart', and 'Table'. + **kwargs: Additional keyword arguments. + + Returns: + None + """ + 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 + [classBand]] + + df_transposed = df.set_index(classBand).T + + if xLabels is not None: + df_transposed["label"] = xLabels + else: + df_transposed["label"] = df_transposed.index + + if classLabels is None: + y_cols = df_transposed.columns.tolist() + y_cols.remove("label") + else: + y_cols = classLabels + + fig = BaseChart(df_transposed) + fig.setChartType( + chart_type, + x_cols="label", + y_cols=y_cols, + **kwargs, + ) + + return fig.chart def image_byRegion(image, regions, reducer, scale, xProperty, **kwargs): @@ -812,8 +882,45 @@ def image_doySeries( endDay, **kwargs, ): - # TODO - pass + """ + Generates a time series chart of an image collection for a specific region over a range of days of the year. + + Args: + imageCollection (ee.ImageCollection): The image collection to analyze. + region (ee.Geometry | ee.FeatureCollection): The region to reduce. + regionReducer (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. + yearReducer (str | ee.Reducer): The reducer type for yearly statistics. + startDay (int): The start day of the year. + endDay (int): The end day of the year. + **kwargs: Additional keyword arguments. + + Returns: + None + """ + series = imageCollection.filter( + ee.Filter.calendarRange(startDay, endDay, "day_of_year") + ) + fc = zonal_stats( + series, + region, + stat_type=regionReducer, + scale=scale, + verbose=False, + return_fc=True, + ) + df = ee_to_df(fc) + years = df["year"].unique() + values = { + year: df[df["year"] == year].drop(columns=["year"]).mean() for year in years + } + + # Creating a dataframe to hold the results + result_df = pd.DataFrame(values).T + + # Plotting the results + line_chart = LineChart(result_df, years.tolist(), **kwargs) + line_chart.plot_chart() def image_doySeriesByRegion( @@ -828,8 +935,39 @@ def image_doySeriesByRegion( endDay, **kwargs, ): - # TODO - pass + """ + Generates a time series chart of an image collection for multiple regions over a range of days of the year. + + Args: + imageCollection (ee.ImageCollection): The image collection to analyze. + bandName (str): The name of the band to analyze. + regions (ee.FeatureCollection): The regions to analyze. + regionReducer (str | ee.Reducer): The reducer type for zonal statistics. + scale (int): The scale in meters at which to perform the analysis. + yearReducer (str | ee.Reducer): The reducer type for yearly statistics. + seriesProperty (str): The property to use for labeling the series. + startDay (int): The start day of the year. + endDay (int): The end day of the year. + **kwargs: Additional keyword arguments. + + Returns: + None + """ + series = imageCollection.filter( + ee.Filter.calendarRange(startDay, endDay, "day_of_year") + ) + fc = zonal_stats( + series, + regions, + stat_type=regionReducer, + scale=scale, + verbose=False, + return_fc=True, + ) + bands = [bandName] + [seriesProperty] + df = ee_to_df(fc)[bands] + line_chart = Feature_ByProperty(df, [bandName], seriesProperty, **kwargs) + line_chart.plot_chart() def image_doySeriesByYear( @@ -843,29 +981,153 @@ def image_doySeriesByYear( endDay, **kwargs, ): - # TODO - pass + """ + Generates a time series chart of an image collection for a specific region over multiple years. + + Args: + imageCollection (ee.ImageCollection): The image collection to analyze. + bandName (str): The name of the band to analyze. + region (ee.Geometry | ee.FeatureCollection): The region to analyze. + regionReducer (str | ee.Reducer): The reducer type for zonal statistics. + scale (int): The scale in meters at which to perform the analysis. + sameDayReducer (str | ee.Reducer): The reducer type for daily statistics. + startDay (int): The start day of the year. + endDay (int): The end day of the year. + **kwargs: Additional keyword arguments. + + Returns: + None + """ + series = imageCollection.filter( + ee.Filter.calendarRange(startDay, endDay, "day_of_year") + ) + fc = zonal_stats( + series, + region, + stat_type=regionReducer, + scale=scale, + verbose=False, + return_fc=True, + ) + bands = [bandName, "year"] + df = ee_to_df(fc)[bands] + line_chart = Feature_ByProperty(df, [bandName], "year", **kwargs) + line_chart.plot_chart() def image_histogram( image, region, scale, maxBuckets, minBucketWidth, maxRaw, maxPixels, **kwargs ): - # TODO - pass + """ + Generates a histogram from an image. + + Args: + image (ee.Image): The image to analyze. + region (ee.Geometry | ee.FeatureCollection): The region to analyze. + scale (int): The scale in meters at which to perform the analysis. + maxBuckets (int): The maximum number of buckets (bins) to use when building a histogram. + minBucketWidth (float): The minimum histogram bucket width, or null to allow any power of 2. + maxRaw (int): The maximum number of pixels to reduce at one time. + maxPixels (int): The maximum number of pixels to reduce. + **kwargs: Additional keyword arguments. + + Returns: + None + """ + fc = zonal_stats( + image, + region, + stat_type="count", + scale=scale, + max_pixels=maxPixels, + max_raw=maxRaw, + verbose=False, + return_fc=True, + ) + df = ee_to_df(fc) + bands = image.bandNames().getInfo() + for band in bands: + feature_histogram(df, band, maxBuckets, minBucketWidth, **kwargs) def image_regions(image, regions, reducer, scale, seriesProperty, xLabels, **kwargs): - # TODO - pass + """ + 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 (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. + seriesProperty (str): The property to use for labeling the series. + xLabels (list): List of x-axis labels. + **kwargs: Additional keyword arguments. + + 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 + [seriesProperty]] + feature_groups(df, seriesProperty, bands, seriesProperty, **kwargs) def image_series(imageCollection, region, reducer, scale, xProperty, **kwargs): - # TODO - pass + """ + Generates a time series chart of an image collection for a specific region. + + Args: + imageCollection (ee.ImageCollection): The image collection to analyze. + region (ee.Geometry | ee.FeatureCollection): The region to reduce. + 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. + xProperty (str): The name of the property to use as the x-axis values. + **kwargs: Additional keyword arguments. + + Returns: + None + """ + fc = zonal_stats( + imageCollection, + region, + stat_type=reducer, + scale=scale, + verbose=False, + return_fc=True, + ) + bands = imageCollection.first().bandNames().getInfo() + df = ee_to_df(fc)[bands + [xProperty]] + feature_byFeature(df, xProperty, bands, **kwargs) def image_seriesByRegion( imageCollection, regions, reducer, band, scale, xProperty, seriesProperty, **kwargs ): - # TODO - pass + """ + Generates a time series chart of an image collection for multiple regions. + + Args: + imageCollection (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. + xProperty (str): The name of the property to use as the x-axis values. + seriesProperty (str): The property to use for labeling the series. + **kwargs: Additional keyword arguments. + + Returns: + None + """ + fc = zonal_stats( + imageCollection, + regions, + stat_type=reducer, + scale=scale, + verbose=False, + return_fc=True, + ) + df = ee_to_df(fc)[[band, xProperty, seriesProperty]] + feature_groups(df, xProperty, band, seriesProperty, **kwargs) From 2abc9448e1901387f5024bc7d9f84076bf712bfb Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Sun, 26 May 2024 09:21:10 -0400 Subject: [PATCH 06/27] Add chart.image_histogram function --- geemap/chart.py | 120 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 97 insertions(+), 23 deletions(-) diff --git a/geemap/chart.py b/geemap/chart.py index ac0ddae2e1..2cfe25272f 100644 --- a/geemap/chart.py +++ b/geemap/chart.py @@ -1016,38 +1016,112 @@ def image_doySeriesByYear( def image_histogram( - image, region, scale, maxBuckets, minBucketWidth, maxRaw, maxPixels, **kwargs -): + image: ee.Image, + region: ee.Geometry, + scale: int, + maxBuckets: int, + minBucketWidth: float, + maxRaw: int, + maxPixels: int, + reducer_args: Dict[str, Any] = {}, + **kwargs: Dict[str, Any], +) -> bq.Figure: """ - Generates a histogram from an image. + Creates a histogram for each band of the specified image within the given + region using bqplot. Args: - image (ee.Image): The image to analyze. - region (ee.Geometry | ee.FeatureCollection): The region to analyze. - scale (int): The scale in meters at which to perform the analysis. - maxBuckets (int): The maximum number of buckets (bins) to use when building a histogram. - minBucketWidth (float): The minimum histogram bucket width, or null to allow any power of 2. - maxRaw (int): The maximum number of pixels to reduce at one time. + 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. + maxBuckets (int): The maximum number of buckets in the histogram. + minBucketWidth (float): The minimum width of the buckets in the histogram. + maxRaw (int): The maximum number of pixels to include in the histogram. maxPixels (int): The maximum number of pixels to reduce. - **kwargs: Additional keyword arguments. + reducer_args (dict): 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: - None + bq.Figure: The bqplot figure containing the histograms. """ - fc = zonal_stats( - image, - region, - stat_type="count", + # Calculate the histogram data. + histogram = image.reduceRegion( + reducer=ee.Reducer.histogram( + maxBuckets=maxBuckets, minBucketWidth=minBucketWidth, maxRaw=maxRaw + ), + geometry=region, scale=scale, - max_pixels=maxPixels, - max_raw=maxRaw, - verbose=False, - return_fc=True, + maxPixels=maxPixels, + **reducer_args, ) - df = ee_to_df(fc) - bands = image.bandNames().getInfo() - for band in bands: - feature_histogram(df, band, maxBuckets, minBucketWidth, **kwargs) + + 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" + ) + + 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()) + + # 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, regions, reducer, scale, seriesProperty, xLabels, **kwargs): From b51f17cf4bfcdc8812ba99e49259e68b8d396e42 Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Sun, 26 May 2024 23:20:52 -0400 Subject: [PATCH 07/27] Add the Chart class --- geemap/chart.py | 267 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 264 insertions(+), 3 deletions(-) diff --git a/geemap/chart.py b/geemap/chart.py index 2cfe25272f..3ee10c07cd 100644 --- a/geemap/chart.py +++ b/geemap/chart.py @@ -20,8 +20,6 @@ class DataTable(pd.DataFrame): - # To ensure compatibility with pandas methods that return DataFrames - _metadata = ["_name"] def __init__( self, @@ -37,10 +35,273 @@ def __init__( **kwargs: Additional keyword arguments to pass to the pd.DataFrame constructor. """ - self._name = kwargs.pop("name", "DataTable") + if isinstance(data, ee.FeatureCollection): + data = ee_to_df(data) + super().__init__(data, **kwargs) +class Chart: + """ + A class to create and display various types of charts from data. + + Attributes: + df (pd.DataFrame): The data to be displayed in the charts. + chart: The bqplot Figure object for the chart. + """ + + def __init__( + self, + data: 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, + xlabel: Optional[str] = None, + ylabel: Optional[str] = None, + options: Optional[Dict[str, Any]] = None, + **kwargs: Any, + ) -> None: + """ + Initializes the Chart with data. + + Args: + data (Union[Dict[str, List[Any]], pd.DataFrame]): The input 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. + xlabel (Optional[str]): The label for the x-axis. Defaults to an + empty string. + ylabel (Optional[str]): The label for the y-axis. Defaults to an + empty string. + options (Optional[Dict[str, Any]]): Additional options for the chart. + **kwargs: Additional keyword arguments to pass to the bqplot Figure + or mark objects. + """ + self.df = DataTable(data) + self.chart_type = chart_type + self.chart = None + self.title = title + self.xlabel = xlabel + self.ylabel = ylabel + self.x_cols = x_cols + self.y_cols = y_cols + self.colors = colors + self.options = options + + 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_title_and_labels() + display(self.figure) + + def _ipython_display_(self) -> None: + """ + Display the chart with toolbar. + """ + self._set_title_and_labels() + plt.show() + + def _set_title_and_labels(self) -> None: + """ + Set the title and labels for the chart. + """ + if self.title is not None: + self.figure.title = self.title + if self.xlabel is not None: + plt.xlabel(self.xlabel) + if self.ylabel is not None: + plt.ylabel(self.ylabel) + + def set_chart_type( + self, + chart_type: str, + clear: bool = True, + **kwargs: Any, + ): + """ + 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.df.columns[0]] + if y_cols is None: + y_cols = [self.df.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 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 + + 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] + + if chart_type == "ScatterChart": + self.chart = plt.scatter( + self.df[x_col].tolist(), + self.df[y_col].tolist(), + colors=[color], + **kwargs, + ) + elif chart_type == "LineChart": + self.chart = plt.plot( + self.df[x_col].tolist(), + self.df[y_col].tolist(), + colors=[color], + **kwargs, + ) + elif chart_type == "ColumnChart": + self.chart = plt.bar( + self.df[x_col].tolist(), + self.df[y_col].tolist(), + colors=[color], + **kwargs, + ) + elif chart_type == "BarChart": + if "orientation" not in kwargs: + kwargs["orientation"] = "horizontal" + self.chart = plt.bar( + self.df[x_col].tolist(), + self.df[y_col].tolist(), + colors=[color], + **kwargs, + ) + elif chart_type == "AreaChart": + if "fill" not in kwargs: + kwargs["fill"] = "bottom" + self.chart = plt.plot( + self.df[x_col].tolist(), + self.df[y_col].tolist(), + colors=[color], + **kwargs, + ) + elif chart_type == "PieChart": + kwargs.pop("labels", None) + self.chart = plt.pie( + sizes=self.df[y_col].tolist(), + labels=self.df[x_col].tolist(), + colors=colors[: len(self.df[x_col])], + **kwargs, + ) + elif chart_type == "Table": + output = widgets.Output(**kwargs) + with output: + display(self.df) + output.layout = widgets.Layout(width="50%") + display(output) + else: + raise ValueError("Unsupported chart type") + + self._set_title_and_labels() + + 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.df + + 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.df = 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 BaseChart: """ A class to create and display various types of charts from data. From 5f4f6b98f69a82fa259a8cbe06198cb394761207 Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Mon, 27 May 2024 00:33:32 -0400 Subject: [PATCH 08/27] Add image_series function --- geemap/chart.py | 111 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 84 insertions(+), 27 deletions(-) diff --git a/geemap/chart.py b/geemap/chart.py index 3ee10c07cd..3a54ccf89c 100644 --- a/geemap/chart.py +++ b/geemap/chart.py @@ -86,7 +86,8 @@ def __init__( empty string. options (Optional[Dict[str, Any]]): Additional options for the chart. **kwargs: Additional keyword arguments to pass to the bqplot Figure - or mark objects. + or mark objects. For axes_options, see + https://bqplot.github.io/bqplot/api/axes """ self.df = DataTable(data) self.chart_type = chart_type @@ -208,22 +209,22 @@ def set_chart_type( if chart_type == "ScatterChart": self.chart = plt.scatter( - self.df[x_col].tolist(), - self.df[y_col].tolist(), + self.df[x_col], + self.df[y_col], colors=[color], **kwargs, ) elif chart_type == "LineChart": self.chart = plt.plot( - self.df[x_col].tolist(), - self.df[y_col].tolist(), + self.df[x_col], + self.df[y_col], colors=[color], **kwargs, ) elif chart_type == "ColumnChart": self.chart = plt.bar( - self.df[x_col].tolist(), - self.df[y_col].tolist(), + self.df[x_col], + self.df[y_col], colors=[color], **kwargs, ) @@ -231,8 +232,8 @@ def set_chart_type( if "orientation" not in kwargs: kwargs["orientation"] = "horizontal" self.chart = plt.bar( - self.df[x_col].tolist(), - self.df[y_col].tolist(), + self.df[x_col], + self.df[y_col], colors=[color], **kwargs, ) @@ -240,16 +241,16 @@ def set_chart_type( if "fill" not in kwargs: kwargs["fill"] = "bottom" self.chart = plt.plot( - self.df[x_col].tolist(), - self.df[y_col].tolist(), + self.df[x_col], + self.df[y_col], colors=[color], **kwargs, ) elif chart_type == "PieChart": kwargs.pop("labels", None) self.chart = plt.pie( - sizes=self.df[y_col].tolist(), - labels=self.df[x_col].tolist(), + sizes=self.df[y_col], + labels=self.df[x_col], colors=colors[: len(self.df[x_col])], **kwargs, ) @@ -1409,32 +1410,88 @@ def image_regions(image, regions, reducer, scale, seriesProperty, xLabels, **kwa feature_groups(df, seriesProperty, bands, seriesProperty, **kwargs) -def image_series(imageCollection, region, reducer, scale, xProperty, **kwargs): +def image_series( + imageCollection, + region, + reducer=None, + scale=None, + xProperty="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, + xlabel: Optional[str] = None, + ylabel: Optional[str] = None, + options: Optional[Dict[str, Any]] = None, + **kwargs: Any, +) -> Chart: """ Generates a time series chart of an image collection for a specific region. Args: imageCollection (ee.ImageCollection): The image collection to analyze. region (ee.Geometry | ee.FeatureCollection): The region to reduce. - reducer (str | ee.Reducer): The reducer type for zonal statistics. Can be one of 'mean', 'median', 'sum', 'min', 'max', etc. + reducer (str | ee.Reducer): The reducer to use. scale (int): The scale in meters at which to perform the analysis. xProperty (str): The name of the property to use as the x-axis values. - **kwargs: Additional keyword arguments. + 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. + xlabel (Optional[str]): The label for the x-axis. Defaults to an + empty string. + ylabel (Optional[str]): The label for the y-axis. Defaults to an + empty string. + options (Optional[Dict[str, Any]]): Additional options for the chart. + **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: - None + Chart: The chart object. + """ - fc = zonal_stats( - imageCollection, - region, - stat_type=reducer, - scale=scale, - verbose=False, - return_fc=True, + + if reducer is None: + reducer = ee.Reducer.mean() + + band_names = imageCollection.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 xProperty == "system:time_start" or xProperty == "system:time_end": + results["date"] = image.date().format("YYYY-MM-dd") + else: + results[xProperty] = image.get(xProperty).getInfo() + + return ee.Feature(None, results) + + # Apply the function over the image collection. + fc = ee.FeatureCollection( + imageCollection.map(get_stats).filter(ee.Filter.notNull(band_names)) ) - bands = imageCollection.first().bandNames().getInfo() - df = ee_to_df(fc)[bands + [xProperty]] - feature_byFeature(df, xProperty, bands, **kwargs) + 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, xlabel, ylabel, options, **kwargs + ) + return fig def image_seriesByRegion( From 2ff67b6be1176cb508e21b24e2cf35b7f4b269b3 Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Mon, 27 May 2024 01:31:49 -0400 Subject: [PATCH 09/27] Add image_seriesByRegion function --- geemap/chart.py | 79 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 66 insertions(+), 13 deletions(-) diff --git a/geemap/chart.py b/geemap/chart.py index 3a54ccf89c..f9c8c6d5e3 100644 --- a/geemap/chart.py +++ b/geemap/chart.py @@ -14,7 +14,7 @@ from bqplot import Tooltip from bqplot import pyplot as plt from IPython.display import display -from .common import ee_to_df, zonal_stats +from .common import ee_to_df, zonal_stats, image_dates from typing import List, Optional, Union, Dict, Any @@ -1495,8 +1495,23 @@ def get_stats(image): def image_seriesByRegion( - imageCollection, regions, reducer, band, scale, xProperty, seriesProperty, **kwargs -): + imageCollection: ee.ImageCollection, + regions: Union[ee.FeatureCollection, ee.Geometry], + reducer: Optional[Union[str, ee.Reducer]] = None, + band: Optional[str] = None, + scale: Optional[int] = None, + xProperty: str = "system:time_start", + seriesProperty: 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, + xlabel: Optional[str] = None, + ylabel: Optional[str] = None, + options: Optional[Dict[str, Any]] = None, + **kwargs: Any, +) -> Chart: """ Generates a time series chart of an image collection for multiple regions. @@ -1508,18 +1523,56 @@ def image_seriesByRegion( scale (int): The scale in meters at which to perform the analysis. xProperty (str): The name of the property to use as the x-axis values. seriesProperty (str): The property to use for labeling the series. - **kwargs: Additional keyword arguments. + 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. + xlabel (Optional[str]): The label for the x-axis. Defaults to an + empty string. + ylabel (Optional[str]): The label for the y-axis. Defaults to an + empty string. + options (Optional[Dict[str, Any]]): Additional options for the chart. + **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: - None + Chart: The chart object. """ + if reducer is None: + reducer = ee.Reducer.mean() + + if band is None: + band = imageCollection.first().bandNames().get(0).getInfo() + + image = imageCollection.select(band).toBands() + fc = zonal_stats( - imageCollection, - regions, - stat_type=reducer, - scale=scale, - verbose=False, - return_fc=True, + image, regions, stat_type=reducer, scale=scale, verbose=False, return_fc=True ) - df = ee_to_df(fc)[[band, xProperty, seriesProperty]] - feature_groups(df, xProperty, band, seriesProperty, **kwargs) + columns = image.bandNames().getInfo() + [seriesProperty] + df = ee_to_df(fc, columns=columns) + + headers = df[seriesProperty].tolist() + df = df.drop(columns=[seriesProperty]).T + df.columns = headers + + if xProperty == "system:time_start" or xProperty == "system:time_end": + indexes = image_dates(imageCollection).getInfo() + df["index"] = pd.to_datetime(indexes) + + else: + indexes = imageCollection.aggregate_array(xProperty).getInfo() + df["index"] = indexes + + fig = Chart( + df, chart_type, x_cols, y_cols, colors, title, xlabel, ylabel, options, **kwargs + ) + return fig From ad42e8c29f26197f9e8c1785b143ea9f97168bc9 Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Wed, 12 Jun 2024 23:02:36 -0400 Subject: [PATCH 10/27] Add transpose_df function --- geemap/chart.py | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/geemap/chart.py b/geemap/chart.py index f9c8c6d5e3..429c98f588 100644 --- a/geemap/chart.py +++ b/geemap/chart.py @@ -41,6 +41,55 @@ def __init__( super().__init__(data, **kwargs) +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 `datatable`. + 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 + + class Chart: """ A class to create and display various types of charts from data. From 1d0d27dd3fe1e9c62636efe94d6a7d0e37cc01b5 Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Wed, 7 Aug 2024 23:43:25 -0400 Subject: [PATCH 11/27] Change function name to snake case --- docs/notebooks/63_charts.ipynb | 4 ++-- examples/notebooks/63_charts.ipynb | 4 ++-- geemap/chart.py | 20 ++++++++++---------- 3 files changed, 14 insertions(+), 14 deletions(-) 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 429c98f588..e1c1964799 100644 --- a/geemap/chart.py +++ b/geemap/chart.py @@ -862,7 +862,7 @@ def get_data(self, image, region, xProperty, reducer, scale): return x_data, y_data -def feature_byFeature( +def feature_by_feature( 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. @@ -887,7 +887,7 @@ def feature_byFeature( raise Exception(e) -def feature_byProperty( +def feature_by_property( features: ee.FeatureCollection, xProperties: Union[list, dict], seriesProperty: str, @@ -1097,7 +1097,7 @@ def grow_bin(bin_size, ref): raise Exception(e) -def image_byClass( +def image_by_class( image, classBand, region, @@ -1159,7 +1159,7 @@ def image_byClass( return fig.chart -def image_byRegion(image, regions, reducer, scale, xProperty, **kwargs): +def image_by_region(image, regions, reducer, scale, xProperty, **kwargs): """ 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. @@ -1169,7 +1169,7 @@ def image_byRegion(image, regions, reducer, scale, xProperty, **kwargs): 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. xProperty (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_byFeature` function. + **kwargs: Additional keyword arguments to be passed to the `feature_by_feature` function. Returns: None @@ -1180,10 +1180,10 @@ def image_byRegion(image, regions, reducer, scale, xProperty, **kwargs): ) bands = image.bandNames().getInfo() df = ee_to_df(fc)[bands + [xProperty]] - feature_byFeature(df, xProperty, bands, **kwargs) + feature_by_feature(df, xProperty, bands, **kwargs) -def image_doySeries( +def image_doy_series( imageCollection, region, regionReducer, @@ -1234,7 +1234,7 @@ def image_doySeries( line_chart.plot_chart() -def image_doySeriesByRegion( +def image_doy_series_by_region( imageCollection, bandName, regions, @@ -1281,7 +1281,7 @@ def image_doySeriesByRegion( line_chart.plot_chart() -def image_doySeriesByYear( +def image_doy_series_by_year( imageCollection, bandName, region, @@ -1543,7 +1543,7 @@ def get_stats(image): return fig -def image_seriesByRegion( +def image_series_by_region( imageCollection: ee.ImageCollection, regions: Union[ee.FeatureCollection, ee.Geometry], reducer: Optional[Union[str, ee.Reducer]] = None, From 85374713c1ca0ae91fd2c58a6d0655943bfd6d97 Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Thu, 8 Aug 2024 10:57:37 -0400 Subject: [PATCH 12/27] Add doy_series_by_year function --- geemap/chart.py | 156 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 124 insertions(+), 32 deletions(-) diff --git a/geemap/chart.py b/geemap/chart.py index e1c1964799..c9ead61525 100644 --- a/geemap/chart.py +++ b/geemap/chart.py @@ -90,6 +90,24 @@ def transpose_df( 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 + + class Chart: """ A class to create and display various types of charts from data. @@ -1281,49 +1299,123 @@ def image_doy_series_by_region( line_chart.plot_chart() -def image_doy_series_by_year( - imageCollection, - bandName, - region, - regionReducer, - scale, - sameDayReducer, - startDay, - endDay, - **kwargs, -): +def doy_series_by_year( + image_collection, + band_name, + region=None, + region_reducer=None, + scale=None, + same_day_reducer=None, + start_day=1, + end_day=366, + chart_type: str = "LineChart", + colors: Optional[List[str]] = None, + title: Optional[str] = None, + xlabel: Optional[str] = None, + ylabel: Optional[str] = None, + options: Optional[Dict[str, Any]] = None, + **kwargs: Any, +) -> Chart: """ - Generates a time series chart of an image collection for a specific region over multiple years. + Generates a time series chart of an image collection for a specific region + over multiple years. Args: - imageCollection (ee.ImageCollection): The image collection to analyze. - bandName (str): The name of the band to analyze. + image_collection (ee.ImageCollection): The image collection to analyze. + band_name (str): The name of the band to analyze. region (ee.Geometry | ee.FeatureCollection): The region to analyze. - regionReducer (str | ee.Reducer): The reducer type for zonal statistics. + region_reducer (str | ee.Reducer): The reducer type for zonal statistics. scale (int): The scale in meters at which to perform the analysis. - sameDayReducer (str | ee.Reducer): The reducer type for daily statistics. - startDay (int): The start day of the year. - endDay (int): The end day of the year. - **kwargs: Additional keyword arguments. + same_day_reducer (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. + xlabel (Optional[str]): The label for the x-axis. Defaults to an + empty string. + ylabel (Optional[str]): The label for the y-axis. Defaults to an + empty string. + options (Optional[Dict[str, Any]]): Additional options for the chart. + **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: None """ - series = imageCollection.filter( - ee.Filter.calendarRange(startDay, endDay, "day_of_year") + + # 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") ) - fc = zonal_stats( - series, - region, - stat_type=regionReducer, - scale=scale, - verbose=False, - return_fc=True, + + # 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"), ) - bands = [bandName, "year"] - df = ee_to_df(fc)[bands] - line_chart = Feature_ByProperty(df, [bandName], "year", **kwargs) - line_chart.plot_chart() + 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, xlabel, ylabel, options, **kwargs + ) + return fig def image_histogram( From ade6b2546cce9f421836c9d1e0af958f3ac5490f Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Thu, 8 Aug 2024 11:30:56 -0400 Subject: [PATCH 13/27] Add image_doy_series function --- geemap/chart.py | 132 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 101 insertions(+), 31 deletions(-) diff --git a/geemap/chart.py b/geemap/chart.py index c9ead61525..d48638541b 100644 --- a/geemap/chart.py +++ b/geemap/chart.py @@ -1202,15 +1202,21 @@ def image_by_region(image, regions, reducer, scale, xProperty, **kwargs): def image_doy_series( - imageCollection, - region, - regionReducer, - scale, - yearReducer, - startDay, - endDay, - **kwargs, -): + image_collection, + region=None, + region_reducer=None, + scale=None, + year_reducer=None, + start_day=1, + end_day=366, + chart_type: str = "LineChart", + colors: Optional[List[str]] = None, + title: Optional[str] = None, + xlabel: Optional[str] = None, + ylabel: Optional[str] = None, + options: Optional[Dict[str, Any]] = 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. @@ -1222,34 +1228,98 @@ def image_doy_series( yearReducer (str | ee.Reducer): The reducer type for yearly statistics. startDay (int): The start day of the year. endDay (int): The end day of the year. - **kwargs: Additional keyword arguments. + 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. + xlabel (Optional[str]): The label for the x-axis. Defaults to an + empty string. + ylabel (Optional[str]): The label for the y-axis. Defaults to an + empty string. + options (Optional[Dict[str, Any]]): Additional options for the chart. + **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: None """ - series = imageCollection.filter( - ee.Filter.calendarRange(startDay, endDay, "day_of_year") - ) - fc = zonal_stats( - series, - region, - stat_type=regionReducer, - scale=scale, - verbose=False, - return_fc=True, - ) - df = ee_to_df(fc) - years = df["year"].unique() - values = { - year: df[df["year"] == year].drop(columns=["year"]).mean() for year in years - } - # Creating a dataframe to hold the results - result_df = pd.DataFrame(values).T + # 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}) - # Plotting the results - line_chart = LineChart(result_df, years.tolist(), **kwargs) - line_chart.plot_chart() + 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 therir 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") + print(y_cols) + + fig = Chart( + df, chart_type, x_cols, y_cols, colors, title, xlabel, ylabel, options, **kwargs + ) + return fig def image_doy_series_by_region( From 8617bfdab62f1a733bbbed0904343c490a245a18 Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Thu, 8 Aug 2024 15:21:23 -0400 Subject: [PATCH 14/27] Add image_doy_series_by_region function --- geemap/chart.py | 137 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 108 insertions(+), 29 deletions(-) diff --git a/geemap/chart.py b/geemap/chart.py index d48638541b..d1937936e9 100644 --- a/geemap/chart.py +++ b/geemap/chart.py @@ -241,6 +241,12 @@ def set_chart_type( 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 = [ @@ -1266,7 +1272,7 @@ def group_by_doy(collection, start, end, reducer): [ee.Feature(None, {"doy": i}) for i in range(start, end + 1)] ) - # Group images by therir day of year. + # 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 @@ -1314,7 +1320,6 @@ def reduce_doy_images(image): x_cols = "doy" y_cols = df.columns.tolist() y_cols.remove("doy") - print(y_cols) fig = Chart( df, chart_type, x_cols, y_cols, colors, title, xlabel, ylabel, options, **kwargs @@ -1323,50 +1328,124 @@ def reduce_doy_images(image): def image_doy_series_by_region( - imageCollection, - bandName, + image_collection, + band_name, regions, - regionReducer, - scale, - yearReducer, - seriesProperty, - startDay, - endDay, - **kwargs, -): + region_reducer=None, + scale=None, + year_reducer=None, + series_property=None, + start_day=1, + end_day=366, + chart_type: str = "LineChart", + colors: Optional[List[str]] = None, + title: Optional[str] = None, + xlabel: Optional[str] = None, + ylabel: Optional[str] = None, + options: Optional[Dict[str, Any]] = 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: - imageCollection (ee.ImageCollection): The image collection to analyze. - bandName (str): The name of the band to analyze. + image_collection (ee.ImageCollection): The image collection to analyze. + band_mame (str): The name of the band to analyze. regions (ee.FeatureCollection): The regions to analyze. - regionReducer (str | ee.Reducer): The reducer type for zonal statistics. + region_reducer (str | ee.Reducer): The reducer type for zonal statistics. scale (int): The scale in meters at which to perform the analysis. - yearReducer (str | ee.Reducer): The reducer type for yearly statistics. - seriesProperty (str): The property to use for labeling the series. - startDay (int): The start day of the year. - endDay (int): The end day of the year. - **kwargs: Additional keyword arguments. + year_reducer (str | ee.Reducer): The reducer type for yearly statistics. + series_property (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. + xlabel (Optional[str]): The label for the x-axis. Defaults to an + empty string. + ylabel (Optional[str]): The label for the y-axis. Defaults to an + empty string. + options (Optional[Dict[str, Any]]): Additional options for the chart. + **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: None """ - series = imageCollection.filter( - ee.Filter.calendarRange(startDay, endDay, "day_of_year") - ) + + 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( - series, + doy_images.toBands(), regions, - stat_type=regionReducer, + stat_type=region_reducer, scale=scale, verbose=False, return_fc=True, ) - bands = [bandName] + [seriesProperty] - df = ee_to_df(fc)[bands] - line_chart = Feature_ByProperty(df, [bandName], seriesProperty, **kwargs) - line_chart.plot_chart() + 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, xlabel, ylabel, options, **kwargs + ) + return fig def doy_series_by_year( From 5e193be277e7736561a9a36b668001057d6b1f71 Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Thu, 8 Aug 2024 18:58:18 -0400 Subject: [PATCH 15/27] Add image_by_class function --- geemap/chart.py | 619 +++++++++++++++--------------------------------- 1 file changed, 188 insertions(+), 431 deletions(-) diff --git a/geemap/chart.py b/geemap/chart.py index d1937936e9..fab5e81448 100644 --- a/geemap/chart.py +++ b/geemap/chart.py @@ -110,7 +110,7 @@ def pivot_df(df: pd.DataFrame, index: str, columns: str, values: str) -> pd.Data class Chart: """ - A class to create and display various types of charts from data. + A class to create and display various types of charts from a data table. Attributes: df (pd.DataFrame): The data to be displayed in the charts. @@ -119,22 +119,21 @@ class Chart: def __init__( self, - data: Union[Dict[str, List[Any]], pd.DataFrame], + 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, - xlabel: Optional[str] = None, - ylabel: Optional[str] = None, - options: Optional[Dict[str, Any]] = None, + x_label: Optional[str] = None, + y_label: Optional[str] = None, **kwargs: Any, ) -> None: """ Initializes the Chart with data. Args: - data (Union[Dict[str, List[Any]], pd.DataFrame]): The input data. + 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', @@ -147,25 +146,23 @@ def __init__( Defaults to a predefined list of colors. title (Optional[str]): The title of the chart. Defaults to the chart type. - xlabel (Optional[str]): The label for the x-axis. Defaults to an + x_label (Optional[str]): The label for the x-axis. Defaults to an empty string. - ylabel (Optional[str]): The label for the y-axis. Defaults to an + y_label (Optional[str]): The label for the y-axis. Defaults to an empty string. - options (Optional[Dict[str, Any]]): Additional options for the chart. **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.df = DataTable(data) + self.data_table = DataTable(data_table) self.chart_type = chart_type self.chart = None self.title = title - self.xlabel = xlabel - self.ylabel = ylabel + self.x_label = x_label + self.y_label = y_label self.x_cols = x_cols self.y_cols = y_cols self.colors = colors - self.options = options if title is not None: kwargs["title"] = title @@ -194,10 +191,10 @@ def _set_title_and_labels(self) -> None: """ if self.title is not None: self.figure.title = self.title - if self.xlabel is not None: - plt.xlabel(self.xlabel) - if self.ylabel is not None: - plt.ylabel(self.ylabel) + if self.x_label is not None: + plt.xlabel(self.x_label) + if self.y_label is not None: + plt.ylabel(self.y_label) def set_chart_type( self, @@ -228,9 +225,9 @@ def set_chart_type( colors = self.colors if x_cols is None: - x_cols = [self.df.columns[0]] + x_cols = [self.data_table.columns[0]] if y_cols is None: - y_cols = [self.df.columns[1]] + y_cols = [self.data_table.columns[1]] if isinstance(x_cols, str): x_cols = [x_cols] @@ -282,22 +279,22 @@ def set_chart_type( if chart_type == "ScatterChart": self.chart = plt.scatter( - self.df[x_col], - self.df[y_col], + self.data_table[x_col], + self.data_table[y_col], colors=[color], **kwargs, ) elif chart_type == "LineChart": self.chart = plt.plot( - self.df[x_col], - self.df[y_col], + self.data_table[x_col], + self.data_table[y_col], colors=[color], **kwargs, ) elif chart_type == "ColumnChart": self.chart = plt.bar( - self.df[x_col], - self.df[y_col], + self.data_table[x_col], + self.data_table[y_col], colors=[color], **kwargs, ) @@ -305,8 +302,8 @@ def set_chart_type( if "orientation" not in kwargs: kwargs["orientation"] = "horizontal" self.chart = plt.bar( - self.df[x_col], - self.df[y_col], + self.data_table[x_col], + self.data_table[y_col], colors=[color], **kwargs, ) @@ -314,23 +311,23 @@ def set_chart_type( if "fill" not in kwargs: kwargs["fill"] = "bottom" self.chart = plt.plot( - self.df[x_col], - self.df[y_col], + self.data_table[x_col], + self.data_table[y_col], colors=[color], **kwargs, ) elif chart_type == "PieChart": kwargs.pop("labels", None) self.chart = plt.pie( - sizes=self.df[y_col], - labels=self.df[x_col], - colors=colors[: len(self.df[x_col])], + sizes=self.data_table[y_col], + labels=self.data_table[x_col], + colors=colors[: len(self.data_table[x_col])], **kwargs, ) elif chart_type == "Table": output = widgets.Output(**kwargs) with output: - display(self.df) + display(self.data_table) output.layout = widgets.Layout(width="50%") display(output) else: @@ -354,7 +351,7 @@ def get_data_table(self) -> DataTable: Returns: DataTable: The DataTable instance containing the chart data. """ - return self.df + return self.data_table def set_data_table(self, data: Union[Dict[str, List[Any]], pd.DataFrame]) -> None: """ @@ -363,7 +360,7 @@ def set_data_table(self, data: Union[Dict[str, List[Any]], pd.DataFrame]) -> Non Args: data (Union[Dict[str, List[Any]], pd.DataFrame]): The new data to be used for the chart. """ - self.df = DataTable(data) + self.data_table = DataTable(data) def set_options(self, **options: Any) -> None: """ @@ -376,247 +373,6 @@ def set_options(self, **options: Any) -> None: setattr(self.figure, key, value) -class BaseChart: - """ - A class to create and display various types of charts from data. - - Attributes: - df (pd.DataFrame): The data to be displayed in the charts. - chart: The bqplot Figure object for the chart. - """ - - def __init__(self, data: Union[Dict, pd.DataFrame], **kwargs: Any) -> None: - """ - Initializes the BaseChart with data. - - Args: - data (dict or pd.DataFrame): The input data. If it's a dictionary, - it will be converted to a DataFrame. - **kwargs: Additional keyword arguments to pass to the pd.DataFrame - constructor. - """ - self.df = DataTable(data) - self.chart = None - self.chart_type = None - - def setChartType( - self, - chart_type: str, - x_cols: Optional[List[str]] = None, - y_cols: Optional[List[str]] = None, - colors: Optional[List[str]] = None, - x_label: Optional[str] = "", - y_label: Optional[str] = "", - title: Optional[str] = None, - **kwargs: Any, - ) -> "BaseChart": - """ - 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'. - x_cols (list of str, optional): The columns to use for the x-axis. - Defaults to the first column. - y_cols (list of str, optional): The columns to use for the y-axis. - Defaults to the second column. - colors (list of str, optional): The colors to use for the chart. - Defaults to a predefined list of colors. - x_label (str, optional): The label for the x-axis. Defaults to an - empty string. - y_label (str, optional): The label for the y-axis. Defaults to an - empty string. - title (str, optional): The title of the chart. Defaults to the - chart type. - **kwargs: Additional keyword arguments to pass to the bqplot Figure - or mark objects. - - Returns: - BaseChart: The BaseChart instance with the chart set. - """ - self.chart_type = chart_type - - if x_cols is None: - x_cols = [self.df.columns[0]] - if y_cols is None: - y_cols = [self.df.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 title is not None: - kwargs["title"] = title - - 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 - - x_sc = bq.OrdinalScale() - y_sc = bq.LinearScale() - - marks = [] - for i, (x_col, y_col) in enumerate(zip(x_cols, y_cols)): - color = colors[ - i % len(colors) - ] # Cycle through colors if not enough are provided - if "display_legend" not in kwargs and len(y_cols) > 1: - kwargs["display_legend"] = True - kwargs["labels"] = [y_col] - else: - kwargs["labels"] = [y_col] - - if chart_type == "ScatterChart": - marks.append( - bq.Scatter( - x=self.df[x_col], - y=self.df[y_col], - scales={"x": x_sc, "y": y_sc}, - colors=[color], - **kwargs, - ) - ) - elif chart_type == "LineChart": - marks.append( - bq.Lines( - x=self.df[x_col], - y=self.df[y_col], - scales={"x": x_sc, "y": y_sc}, - colors=[color], - **kwargs, - ) - ) - elif chart_type == "ColumnChart": - marks.append( - bq.Bars( - x=self.df[x_col], - y=self.df[y_col], - scales={"x": x_sc, "y": y_sc}, - colors=[color], - **kwargs, - ) - ) - elif chart_type == "BarChart": - if "orientation" not in kwargs: - kwargs["orientation"] = "horizontal" - marks.append( - bq.Bars( - x=self.df[x_col], - y=self.df[y_col], - scales={"x": x_sc, "y": y_sc}, - colors=[color], - **kwargs, - ) - ) - elif chart_type == "AreaChart": - if "fill" not in kwargs: - kwargs["fill"] = "bottom" - marks.append( - bq.Lines( - x=self.df[x_col], - y=self.df[y_col], - scales={"x": x_sc, "y": y_sc}, - colors=[color], - **kwargs, - ) - ) - elif chart_type == "PieChart": - # Pie chart does not support multiple series in the same way; use only the first pair of x_col and y_col - self.chart = bq.Figure(title=title, **kwargs) - pie = bq.Pie( - sizes=self.df[y_cols[0]].tolist(), - labels=self.df[x_cols[0]].tolist(), - colors=colors[: len(self.df[x_cols[0]])], - **kwargs, - ) - self.chart.marks = [pie] - return self - elif chart_type == "Table": - self.chart = widgets.Output(**kwargs) - with self.chart: - display(self.df) - self.chart.layout = widgets.Layout(width="50%") - return self - else: - raise ValueError("Unsupported chart type") - - x_axis = bq.Axis(scale=x_sc, label=x_label) - y_axis = bq.Axis(scale=y_sc, orientation="vertical", label=y_label) - self.chart = bq.Figure(marks=marks, axes=[x_axis, y_axis], **kwargs) - - return self - - def getChartType(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 getDataTable(self) -> DataTable: - """ - Get the DataTable used by the chart. - - Returns: - DataTable: The DataTable instance containing the chart data. - """ - return self.df - - def setDataTable(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.df = DataTable(data) - - def setOptions(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.chart, key, value) - - def display(self) -> None: - """ - Displays the chart. - """ - display(self.chart) - - class BaseChartClass: """This should include everything a chart module requires to plot figures.""" @@ -628,8 +384,8 @@ 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 @@ -681,9 +437,9 @@ def __init__(self, features, default_labels, name, type="grouped", **kwargs): self.type = type def generate_tooltip(self): - if (self.xlabel is not None) and (self.ylabel is not None): + 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"]) @@ -709,6 +465,8 @@ def plot_chart(self): legend_location=self.legend_location, ) + print(self.x_data, self.y_data) + self.bar_chart = plt.bar( self.x_data, self.y_data, @@ -718,10 +476,10 @@ def plot_chart(self): self.generate_tooltip() plt.ylim(*self.get_ylim()) - if self.xlabel: - plt.xlabel(self.xlabel) - if self.ylabel: - 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 @@ -754,10 +512,10 @@ def plot_chart(self): self.generate_tooltip() plt.ylim(*self.get_ylim()) - if self.xlabel: - plt.xlabel(self.xlabel) - if self.ylabel: - 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 @@ -850,44 +608,8 @@ def get_data(self, xProperty, new_column_names): return x_data, y_data -class Image_byClass(LineChart): - """A object to define variables and get_data method.""" - - def __init__( - self, - image, - region, - reducer, - scale, - classLabels, - xLabels, - xProperty, - name="image.byClass", - **kwargs, - ): - self.classLabels = classLabels - self.xLabels = xLabels - super().__init__(image, classLabels, name, **kwargs) - self.x_data, self.y_data = self.get_data( - image, region, xProperty, reducer, scale - ) - - def get_data(self, image, region, xProperty, reducer, scale): - 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 + [xProperty]] - columns = df.columns.tolist() - columns.remove(xProperty) - x_data = columns - y_data = df.drop([xProperty], axis=1).to_numpy() - - return x_data, y_data - - def feature_by_feature( - features: ee.FeatureCollection, xProperty: str, yProperties: list, **kwargs + features: ee.FeatureCollection, x_property: str, y_properties: list, **kwargs ): """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 @@ -901,7 +623,7 @@ def feature_by_feature( Exception: Errors when creating the chart. """ bar = Feature_ByFeature( - features=features, xProperty=xProperty, yProperties=yProperties, **kwargs + features=features, xProperty=x_property, yProperties=y_properties, **kwargs ) try: @@ -913,8 +635,8 @@ def feature_by_feature( 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. @@ -922,9 +644,9 @@ def feature_by_property( 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 + 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. - seriesProperty (str): The name of the property used to label each feature in the legend. + 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. @@ -932,8 +654,8 @@ def feature_by_property( """ bar = Feature_ByProperty( features=features, - xProperties=xProperties, - seriesProperty=seriesProperty, + xProperties=x_properties, + seriesProperty=series_property, **kwargs, ) @@ -944,25 +666,25 @@ def feature_by_property( raise Exception(e) -def feature_groups(features, xProperty, yProperty, seriesProperty, **kwargs): +def feature_groups(features, x_property, y_property, series_property, **kwargs): """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. Raises: Exception: Errors when creating the chart. """ bar = Feature_Groups( features=features, - xProperty=xProperty, - yProperty=yProperty, - seriesProperty=seriesProperty, + xProperty=x_property, + yProperty=y_property, + seriesProperty=series_property, **kwargs, ) @@ -974,7 +696,7 @@ def feature_groups(features, xProperty, yProperty, seriesProperty, **kwargs): def feature_histogram( - features, property, maxBuckets=None, minBucketWidth=None, show=True, **kwargs + features, property, max_buckets=None, min_bucket_width=None, show=True, **kwargs ): """ Generates a Chart from a set of features. @@ -986,12 +708,15 @@ 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. Raises: Exception: If the provided xProperties is not a list or dict. @@ -1032,22 +757,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 @@ -1077,20 +802,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: @@ -1104,10 +829,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"]) @@ -1123,12 +848,12 @@ def grow_bin(bin_size, ref): def image_by_class( image, - classBand, + class_band, region, reducer="MEAN", scale=None, - classLabels=None, - xLabels=None, + class_labels=None, + x_labels=None, chart_type="LineChart", **kwargs, ): @@ -1138,13 +863,13 @@ def image_by_class( Args: image (ee.Image): Image to extract band values from. - classBand (str): The band name to use as class labels. + 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): 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. - classLabels (list): List of class labels. - xLabels (list): List of x-axis labels. + class_labels (list): List of class labels. + x_labels (list): List of x-axis labels. chart_type (str): The type of chart to create. Supported types are 'ScatterChart', 'LineChart', 'ColumnChart', 'BarChart', 'PieChart', 'AreaChart', and 'Table'. @@ -1157,33 +882,28 @@ def image_by_class( image, region, stat_type=reducer, scale=scale, verbose=False, return_fc=True ) bands = image.bandNames().getInfo() - df = ee_to_df(fc)[bands + [classBand]] + df = ee_to_df(fc)[bands + [class_band]] - df_transposed = df.set_index(classBand).T + df_transposed = df.set_index(class_band).T - if xLabels is not None: - df_transposed["label"] = xLabels + if x_labels is not None: + df_transposed["label"] = x_labels else: df_transposed["label"] = df_transposed.index - if classLabels is None: + if class_labels is None: y_cols = df_transposed.columns.tolist() y_cols.remove("label") else: - y_cols = classLabels + y_cols = class_labels - fig = BaseChart(df_transposed) - fig.setChartType( - chart_type, - x_cols="label", - y_cols=y_cols, - **kwargs, + fig = Chart( + df_transposed, chart_type=chart_type, x_cols="label", y_cols=y_cols, **kwargs ) - - return fig.chart + return fig -def image_by_region(image, regions, reducer, scale, xProperty, **kwargs): +def image_by_region(image, regions, reducer, scale, x_property, **kwargs): """ 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. @@ -1192,7 +912,7 @@ def image_by_region(image, regions, reducer, scale, xProperty, **kwargs): 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. - xProperty (str): The name of the property in the feature collection to use as the x-axis values. + 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: @@ -1203,8 +923,8 @@ def image_by_region(image, regions, reducer, scale, xProperty, **kwargs): image, regions, stat_type=reducer, scale=scale, verbose=False, return_fc=True ) bands = image.bandNames().getInfo() - df = ee_to_df(fc)[bands + [xProperty]] - feature_by_feature(df, xProperty, bands, **kwargs) + df = ee_to_df(fc)[bands + [x_property]] + feature_by_feature(df, x_property, bands, **kwargs) def image_doy_series( @@ -1218,22 +938,22 @@ def image_doy_series( chart_type: str = "LineChart", colors: Optional[List[str]] = None, title: Optional[str] = None, - xlabel: Optional[str] = None, - ylabel: Optional[str] = None, - options: Optional[Dict[str, Any]] = 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: - imageCollection (ee.ImageCollection): The image collection to analyze. + image_collection (ee.ImageCollection): The image collection to analyze. region (ee.Geometry | ee.FeatureCollection): The region to reduce. - regionReducer (str | ee.Reducer): The reducer type for zonal statistics. Can be one of 'mean', 'median', 'sum', 'min', 'max', etc. + region_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. - yearReducer (str | ee.Reducer): The reducer type for yearly statistics. - startDay (int): The start day of the year. - endDay (int): The end day of the year. + year_reducer (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'. @@ -1241,11 +961,10 @@ def image_doy_series( Defaults to a predefined list of colors. title (Optional[str]): The title of the chart. Defaults to the chart type. - xlabel (Optional[str]): The label for the x-axis. Defaults to an + x_label (Optional[str]): The label for the x-axis. Defaults to an empty string. - ylabel (Optional[str]): The label for the y-axis. Defaults to an + y_label (Optional[str]): The label for the y-axis. Defaults to an empty string. - options (Optional[Dict[str, Any]]): Additional options for the chart. **kwargs: Additional keyword arguments to pass to the bqplot Figure or mark objects. For axes_options, see https://bqplot.github.io/bqplot/api/axes @@ -1322,7 +1041,15 @@ def reduce_doy_images(image): y_cols.remove("doy") fig = Chart( - df, chart_type, x_cols, y_cols, colors, title, xlabel, ylabel, options, **kwargs + df, + chart_type, + x_cols, + y_cols, + colors, + title, + x_label, + y_label, + **kwargs, ) return fig @@ -1340,9 +1067,8 @@ def image_doy_series_by_region( chart_type: str = "LineChart", colors: Optional[List[str]] = None, title: Optional[str] = None, - xlabel: Optional[str] = None, - ylabel: Optional[str] = None, - options: Optional[Dict[str, Any]] = None, + x_label: Optional[str] = None, + y_label: Optional[str] = None, **kwargs: Any, ) -> Chart: """ @@ -1365,11 +1091,10 @@ def image_doy_series_by_region( Defaults to a predefined list of colors. title (Optional[str]): The title of the chart. Defaults to the chart type. - xlabel (Optional[str]): The label for the x-axis. Defaults to an + x_label (Optional[str]): The label for the x-axis. Defaults to an empty string. - ylabel (Optional[str]): The label for the y-axis. Defaults to an + y_label (Optional[str]): The label for the y-axis. Defaults to an empty string. - options (Optional[Dict[str, Any]]): Additional options for the chart. **kwargs: Additional keyword arguments to pass to the bqplot Figure or mark objects. For axes_options, see https://bqplot.github.io/bqplot/api/axes @@ -1443,7 +1168,15 @@ def reduce_images(doy): y_cols.remove("doy") fig = Chart( - df, chart_type, "doy", y_cols, colors, title, xlabel, ylabel, options, **kwargs + df, + chart_type, + "doy", + y_cols, + colors, + title, + x_label, + y_label, + **kwargs, ) return fig @@ -1460,9 +1193,8 @@ def doy_series_by_year( chart_type: str = "LineChart", colors: Optional[List[str]] = None, title: Optional[str] = None, - xlabel: Optional[str] = None, - ylabel: Optional[str] = None, - options: Optional[Dict[str, Any]] = None, + x_label: Optional[str] = None, + y_label: Optional[str] = None, **kwargs: Any, ) -> Chart: """ @@ -1485,11 +1217,10 @@ def doy_series_by_year( Defaults to a predefined list of colors. title (Optional[str]): The title of the chart. Defaults to the chart type. - xlabel (Optional[str]): The label for the x-axis. Defaults to an + x_label (Optional[str]): The label for the x-axis. Defaults to an empty string. - ylabel (Optional[str]): The label for the y-axis. Defaults to an + y_label (Optional[str]): The label for the y-axis. Defaults to an empty string. - options (Optional[Dict[str, Any]]): Additional options for the chart. **kwargs: Additional keyword arguments to pass to the bqplot Figure or mark objects. For axes_options, see https://bqplot.github.io/bqplot/api/axes @@ -1562,7 +1293,15 @@ def reduce_features(doy_year): x_cols = "doy" fig = Chart( - df, chart_type, x_cols, y_cols, colors, title, xlabel, ylabel, options, **kwargs + df, + chart_type, + x_cols, + y_cols, + colors, + title, + x_label, + y_label, + **kwargs, ) return fig @@ -1701,18 +1440,18 @@ def image_regions(image, regions, reducer, scale, seriesProperty, xLabels, **kwa def image_series( - imageCollection, + image_collection, region, reducer=None, scale=None, - xProperty="system:time_start", + x_property="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, - xlabel: Optional[str] = None, - ylabel: Optional[str] = None, + x_label: Optional[str] = None, + y_label: Optional[str] = None, options: Optional[Dict[str, Any]] = None, **kwargs: Any, ) -> Chart: @@ -1736,9 +1475,9 @@ def image_series( Defaults to a predefined list of colors. title (Optional[str]): The title of the chart. Defaults to the chart type. - xlabel (Optional[str]): The label for the x-axis. Defaults to an + x_label (Optional[str]): The label for the x-axis. Defaults to an empty string. - ylabel (Optional[str]): The label for the y-axis. Defaults to an + y_label (Optional[str]): The label for the y-axis. Defaults to an empty string. options (Optional[Dict[str, Any]]): Additional options for the chart. **kwargs: Additional keyword arguments to pass to the bqplot Figure @@ -1753,7 +1492,7 @@ def image_series( if reducer is None: reducer = ee.Reducer.mean() - band_names = imageCollection.first().bandNames().getInfo() + band_names = image_collection.first().bandNames().getInfo() # Function to reduce the region and get the mean for each image. def get_stats(image): @@ -1763,23 +1502,32 @@ def get_stats(image): for band in band_names: results[band] = stats.get(band) - if xProperty == "system:time_start" or xProperty == "system:time_end": + if x_property == "system:time_start" or x_property == "system:time_end": results["date"] = image.date().format("YYYY-MM-dd") else: - results[xProperty] = image.get(xProperty).getInfo() + results[x_property] = image.get(x_property).getInfo() return ee.Feature(None, results) # Apply the function over the image collection. fc = ee.FeatureCollection( - imageCollection.map(get_stats).filter(ee.Filter.notNull(band_names)) + 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, xlabel, ylabel, options, **kwargs + df, + chart_type, + x_cols, + y_cols, + colors, + title, + x_label, + y_label, + options, + **kwargs, ) return fig @@ -1797,8 +1545,8 @@ def image_series_by_region( y_cols: Optional[List[str]] = None, colors: Optional[List[str]] = None, title: Optional[str] = None, - xlabel: Optional[str] = None, - ylabel: Optional[str] = None, + x_label: Optional[str] = None, + y_label: Optional[str] = None, options: Optional[Dict[str, Any]] = None, **kwargs: Any, ) -> Chart: @@ -1824,9 +1572,9 @@ def image_series_by_region( Defaults to a predefined list of colors. title (Optional[str]): The title of the chart. Defaults to the chart type. - xlabel (Optional[str]): The label for the x-axis. Defaults to an + x_label (Optional[str]): The label for the x-axis. Defaults to an empty string. - ylabel (Optional[str]): The label for the y-axis. Defaults to an + y_label (Optional[str]): The label for the y-axis. Defaults to an empty string. options (Optional[Dict[str, Any]]): Additional options for the chart. **kwargs: Additional keyword arguments to pass to the bqplot Figure @@ -1863,6 +1611,15 @@ def image_series_by_region( df["index"] = indexes fig = Chart( - df, chart_type, x_cols, y_cols, colors, title, xlabel, ylabel, options, **kwargs + df, + chart_type, + x_cols, + y_cols, + colors, + title, + x_label, + y_label, + options, + **kwargs, ) return fig From 218dc5d29dbe048fa4740814d2245799a02870d6 Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Thu, 8 Aug 2024 19:38:53 -0400 Subject: [PATCH 16/27] Add typehints and docstrings --- geemap/chart.py | 621 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 427 insertions(+), 194 deletions(-) diff --git a/geemap/chart.py b/geemap/chart.py index fab5e81448..d45a19cbb0 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. """ # *******************************************************************************# @@ -16,7 +16,7 @@ from IPython.display import display from .common import ee_to_df, zonal_stats, image_dates -from typing import List, Optional, Union, Dict, Any +from typing import List, Optional, Union, Dict, Any, Tuple class DataTable(pd.DataFrame): @@ -64,7 +64,7 @@ def transpose_df( pd.DataFrame: The transposed DataFrame. Raises: - ValueError: If `label_col` is not a column in `datatable`. + 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. """ @@ -201,7 +201,7 @@ def set_chart_type( chart_type: str, clear: bool = True, **kwargs: Any, - ): + ) -> None: """ Sets the chart type and other chart properties. @@ -358,7 +358,8 @@ def set_data_table(self, data: Union[Dict[str, List[Any]], pd.DataFrame]) -> Non 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. + data (Union[Dict[str, List[Any]], pd.DataFrame]): The new data to be + used for the chart. """ self.data_table = DataTable(data) @@ -376,7 +377,22 @@ def set_options(self, **options: Any) -> None: 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 = "" @@ -418,25 +434,58 @@ 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): + 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.x_label, self.y_label] @@ -444,7 +493,13 @@ def generate_tooltip(self): 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: @@ -459,7 +514,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, @@ -495,10 +553,28 @@ def plot_chart(self): class LineChart(BarChart): """A class to define variables and get_data method for a line chart.""" - def __init__(self, features, labels, name="line.chart", **kwargs): + 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): + def plot_chart(self) -> None: + """ + Plots the line chart. + """ fig = plt.figure( title=self.title, legend_location=self.legend_location, @@ -526,109 +602,215 @@ def plot_chart(self): 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) + self.x_data, self.y_data = self.get_data(x_property, y_properties) - def get_data(self, xProperty, yProperties): - x_data = list(self.df[xProperty]) - y_data = list(self.df[yProperties].values.T) + 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) + self.labels = list(self.df[series_property]) + self.x_data, self.y_data = self.get_data(x_properties) - 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 + 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. + + Args: + series_property (str): The property to use for labeling the series. + y_property (str): The property to use for the y-axis. - def get_column_names(self, seriesProperty, yProperty): + 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_by_feature( - features: ee.FeatureCollection, x_property: str, y_properties: list, **kwargs -): - """Generates a Chart from a set of features. Plots the value of one or more properties for each 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=x_property, yProperties=y_properties, **kwargs + features=features, x_property=x_property, y_properties=y_properties, **kwargs ) try: bar.plot_chart() - except Exception as e: raise Exception(e) @@ -639,14 +821,17 @@ def feature_by_property( 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. - 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. + 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. @@ -654,8 +839,8 @@ def feature_by_property( """ bar = Feature_ByProperty( features=features, - xProperties=x_properties, - seriesProperty=series_property, + x_properties=x_properties, + series_property=series_property, **kwargs, ) @@ -666,25 +851,36 @@ def feature_by_property( raise Exception(e) -def feature_groups(features, x_property, y_property, series_property, **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. 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=x_property, - yProperty=y_property, - seriesProperty=series_property, + x_property=x_property, + y_property=y_property, + series_property=series_property, **kwargs, ) @@ -696,8 +892,13 @@ def feature_groups(features, x_property, y_property, series_property, **kwargs): def feature_histogram( - features, property, max_buckets=None, min_bucket_width=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. @@ -709,7 +910,7 @@ def feature_histogram( Args: features (ee.FeatureCollection): The features to include in the chart. - property (str): The name of the property to generate the histogram for. + 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, @@ -717,10 +918,14 @@ def feature_histogram( 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 @@ -847,36 +1052,35 @@ def grow_bin(bin_size, ref): def image_by_class( - image, - class_band, - region, - reducer="MEAN", - scale=None, - class_labels=None, - x_labels=None, - chart_type="LineChart", - **kwargs, -): + 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. + 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): 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. - class_labels (list): List of class labels. - x_labels (list): List of x-axis labels. - chart_type (str): The type of chart to create. Supported types are + 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'. + 'AreaChart', and 'Table'. Defaults to 'LineChart'. **kwargs: Additional keyword arguments. Returns: - None + Any: The generated chart. """ fc = zonal_stats( image, region, stat_type=reducer, scale=scale, verbose=False, return_fc=True @@ -903,17 +1107,29 @@ def image_by_class( return fig -def image_by_region(image, regions, reducer, scale, x_property, **kwargs): +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. + 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. + 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. + 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 @@ -928,13 +1144,13 @@ def image_by_region(image, regions, reducer, scale, x_property, **kwargs): def image_doy_series( - image_collection, - region=None, - region_reducer=None, - scale=None, - year_reducer=None, - start_day=1, - end_day=366, + 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, @@ -943,15 +1159,18 @@ def image_doy_series( **kwargs: Any, ) -> Chart: """ - Generates a time series chart of an image collection for a specific region over a range of days of the year. + 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 (ee.Geometry | ee.FeatureCollection): The region to reduce. - region_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. - year_reducer (str | ee.Reducer): The reducer type for yearly statistics. + 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 @@ -970,7 +1189,7 @@ def image_doy_series( https://bqplot.github.io/bqplot/api/axes Returns: - None + Chart: The generated chart. """ # Function to add day-of-year ('doy') and year properties to each image. @@ -1055,15 +1274,15 @@ def reduce_doy_images(image): def image_doy_series_by_region( - image_collection, - band_name, - regions, - region_reducer=None, - scale=None, - year_reducer=None, - series_property=None, - start_day=1, - end_day=366, + 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, @@ -1072,16 +1291,19 @@ def image_doy_series_by_region( **kwargs: Any, ) -> Chart: """ - Generates a time series chart of an image collection for multiple regions over a range of days of the year. + 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_mame (str): The name of the band to analyze. + band_name (str): The name of the band to analyze. regions (ee.FeatureCollection): The regions to analyze. - region_reducer (str | ee.Reducer): The reducer type for zonal statistics. - scale (int): The scale in meters at which to perform the analysis. - year_reducer (str | ee.Reducer): The reducer type for yearly statistics. - series_property (str): The property to use for labeling the series. + 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 @@ -1100,7 +1322,7 @@ def image_doy_series_by_region( https://bqplot.github.io/bqplot/api/axes Returns: - None + Chart: The generated chart. """ image_collection = image_collection.select(band_name) @@ -1182,14 +1404,14 @@ def reduce_images(doy): def doy_series_by_year( - image_collection, - band_name, - region=None, - region_reducer=None, - scale=None, - same_day_reducer=None, - start_day=1, - end_day=366, + 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, @@ -1199,15 +1421,18 @@ def doy_series_by_year( ) -> Chart: """ Generates a time series chart of an image collection for a specific region - over multiple years. + over multiple years. Args: image_collection (ee.ImageCollection): The image collection to analyze. band_name (str): The name of the band to analyze. - region (ee.Geometry | ee.FeatureCollection): The region to analyze. - region_reducer (str | ee.Reducer): The reducer type for zonal statistics. - scale (int): The scale in meters at which to perform the analysis. - same_day_reducer (str | ee.Reducer): The reducer type for daily statistics. + 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 @@ -1226,7 +1451,7 @@ def doy_series_by_year( https://bqplot.github.io/bqplot/api/axes Returns: - None + Chart: The generated chart. """ # Function to add day-of-year ('doy') and year properties to each image. @@ -1310,30 +1535,30 @@ def image_histogram( image: ee.Image, region: ee.Geometry, scale: int, - maxBuckets: int, - minBucketWidth: float, - maxRaw: int, - maxPixels: 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. + 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. - maxBuckets (int): The maximum number of buckets in the histogram. - minBucketWidth (float): The minimum width of the buckets in the histogram. - maxRaw (int): The maximum number of pixels to include in the histogram. - maxPixels (int): The maximum number of pixels to reduce. - reducer_args (dict): Additional arguments to pass to the image.reduceRegion. + 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. + 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. @@ -1343,11 +1568,11 @@ def image_histogram( # Calculate the histogram data. histogram = image.reduceRegion( reducer=ee.Reducer.histogram( - maxBuckets=maxBuckets, minBucketWidth=minBucketWidth, maxRaw=maxRaw + maxBuckets=max_buckets, minBucketWidth=min_bucket_width, maxRaw=max_raw ), geometry=region, scale=scale, - maxPixels=maxPixels, + maxPixels=max_pixels, **reducer_args, ) @@ -1415,36 +1640,47 @@ def create_histogram( return combined_fig -def image_regions(image, regions, reducer, scale, seriesProperty, xLabels, **kwargs): +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. + 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 (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. + 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. - seriesProperty (str): The property to use for labeling the series. - xLabels (list): List of x-axis labels. + 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: - None + bq.Figure: The bqplot figure. """ 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 + [seriesProperty]] - feature_groups(df, seriesProperty, bands, seriesProperty, **kwargs) + df = ee_to_df(fc)[bands + [series_property]] + feature_groups(df, series_property, bands, series_property, **kwargs) def image_series( - image_collection, - region, - reducer=None, - scale=None, - x_property="system:time_start", + 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, @@ -1459,11 +1695,11 @@ def image_series( Generates a time series chart of an image collection for a specific region. Args: - imageCollection (ee.ImageCollection): The image collection to analyze. - region (ee.Geometry | ee.FeatureCollection): The region to reduce. - reducer (str | ee.Reducer): The reducer to use. - scale (int): The scale in meters at which to perform the analysis. - xProperty (str): The name of the property to use as the x-axis values. + 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'. @@ -1486,7 +1722,6 @@ def image_series( Returns: Chart: The chart object. - """ if reducer is None: @@ -1533,13 +1768,13 @@ def get_stats(image): def image_series_by_region( - imageCollection: ee.ImageCollection, + 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, - xProperty: str = "system:time_start", - seriesProperty: str = "system:index", + 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, @@ -1547,20 +1782,19 @@ def image_series_by_region( title: Optional[str] = None, x_label: Optional[str] = None, y_label: Optional[str] = None, - options: Optional[Dict[str, Any]] = None, **kwargs: Any, ) -> Chart: """ Generates a time series chart of an image collection for multiple regions. Args: - imageCollection (ee.ImageCollection): The image collection to analyze. + 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. - xProperty (str): The name of the property to use as the x-axis values. - seriesProperty (str): The property to use for labeling the series. + 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'. @@ -1588,26 +1822,26 @@ def image_series_by_region( reducer = ee.Reducer.mean() if band is None: - band = imageCollection.first().bandNames().get(0).getInfo() + band = image_collection.first().bandNames().get(0).getInfo() - image = imageCollection.select(band).toBands() + 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() + [seriesProperty] + columns = image.bandNames().getInfo() + [series_property] df = ee_to_df(fc, columns=columns) - headers = df[seriesProperty].tolist() - df = df.drop(columns=[seriesProperty]).T + headers = df[series_property].tolist() + df = df.drop(columns=[series_property]).T df.columns = headers - if xProperty == "system:time_start" or xProperty == "system:time_end": - indexes = image_dates(imageCollection).getInfo() + 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 = imageCollection.aggregate_array(xProperty).getInfo() + indexes = image_collection.aggregate_array(x_property).getInfo() df["index"] = indexes fig = Chart( @@ -1619,7 +1853,6 @@ def image_series_by_region( title, x_label, y_label, - options, **kwargs, ) return fig From df81be4d47e8e5a100c319b1b5e707b74381a6ce Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Thu, 8 Aug 2024 20:27:42 -0400 Subject: [PATCH 17/27] Add notebook template --- docs/notebooks/144_chart_features.ipynb | 31 +++++++++++++++++++ docs/notebooks/145_chart_image.ipynb | 31 +++++++++++++++++++ .../146_chart_image_collection.ipynb | 31 +++++++++++++++++++ docs/notebooks/147_chart_array_list.ipynb | 31 +++++++++++++++++++ docs/notebooks/148_chart_data_table.ipynb | 31 +++++++++++++++++++ mkdocs.yml | 5 +++ 6 files changed, 160 insertions(+) create mode 100644 docs/notebooks/144_chart_features.ipynb create mode 100644 docs/notebooks/145_chart_image.ipynb create mode 100644 docs/notebooks/146_chart_image_collection.ipynb create mode 100644 docs/notebooks/147_chart_array_list.ipynb create mode 100644 docs/notebooks/148_chart_data_table.ipynb diff --git a/docs/notebooks/144_chart_features.ipynb b/docs/notebooks/144_chart_features.ipynb new file mode 100644 index 0000000000..ff0699d966 --- /dev/null +++ b/docs/notebooks/144_chart_features.ipynb @@ -0,0 +1,31 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\"Open\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" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "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..19dbf5b01a --- /dev/null +++ b/docs/notebooks/145_chart_image.ipynb @@ -0,0 +1,31 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\"Open\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" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "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..6ed10dfb79 --- /dev/null +++ b/docs/notebooks/146_chart_image_collection.ipynb @@ -0,0 +1,31 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\"Open\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" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "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..fc9ce8bd0d --- /dev/null +++ b/docs/notebooks/147_chart_array_list.ipynb @@ -0,0 +1,31 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\"Open\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" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "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..df6bf4acb4 --- /dev/null +++ b/docs/notebooks/148_chart_data_table.ipynb @@ -0,0 +1,31 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\"Open\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" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/mkdocs.yml b/mkdocs.yml index 8bb97419f0..25aa43ad61 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -297,6 +297,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 # - miscellaneous: # - notebooks/cartoee_colab.ipynb # - notebooks/cartoee_colorbar.ipynb From 4bcca8c5c52c7bb921ffff0e81481892beb2d3c5 Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Fri, 9 Aug 2024 21:34:39 -0400 Subject: [PATCH 18/27] Add chart_features.ipynb --- docs/notebooks/144_chart_features.ipynb | 319 +++++++++++++++++++++++- geemap/chart.py | 2 - 2 files changed, 318 insertions(+), 3 deletions(-) diff --git a/docs/notebooks/144_chart_features.ipynb b/docs/notebooks/144_chart_features.ipynb index ff0699d966..362896da41 100644 --- a/docs/notebooks/144_chart_features.ipynb +++ b/docs/notebooks/144_chart_features.ipynb @@ -19,11 +19,328 @@ "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": [ + "## 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 = [\n", + " \"Jan\",\n", + " \"Feb\",\n", + " \"Mar\",\n", + " \"Apr\",\n", + " \"May\",\n", + " \"Jun\",\n", + " \"Jul\",\n", + " \"Aug\",\n", + " \"Sep\",\n", + " \"Oct\",\n", + " \"Nov\",\n", + " \"Dec\",\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": [ + "## 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 = [\n", + " \"Jan\",\n", + " \"Feb\",\n", + " \"Mar\",\n", + " \"Apr\",\n", + " \"May\",\n", + " \"Jun\",\n", + " \"Jul\",\n", + " \"Aug\",\n", + " \"Sep\",\n", + " \"Oct\",\n", + " \"Nov\",\n", + " \"Dec\",\n", + "]" + ] + }, + { + "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": [ + "## 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": [ + "## 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" + ] } ], "metadata": { + "kernelspec": { + "display_name": "geo", + "language": "python", + "name": "python3" + }, "language_info": { - "name": "python" + "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, diff --git a/geemap/chart.py b/geemap/chart.py index d45a19cbb0..5c91b5b5a4 100644 --- a/geemap/chart.py +++ b/geemap/chart.py @@ -523,8 +523,6 @@ def plot_chart(self) -> None: legend_location=self.legend_location, ) - print(self.x_data, self.y_data) - self.bar_chart = plt.bar( self.x_data, self.y_data, From 74f6c442538c48ea2d93186025ceb7bb3e1d955f Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Fri, 9 Aug 2024 22:29:11 -0400 Subject: [PATCH 19/27] Add chart_image.ipynb --- docs/notebooks/144_chart_features.ipynb | 32 +-- docs/notebooks/145_chart_image.ipynb | 247 +++++++++++++++++++++++- geemap/chart.py | 4 +- 3 files changed, 252 insertions(+), 31 deletions(-) diff --git a/docs/notebooks/144_chart_features.ipynb b/docs/notebooks/144_chart_features.ipynb index 362896da41..928f65360d 100644 --- a/docs/notebooks/144_chart_features.ipynb +++ b/docs/notebooks/144_chart_features.ipynb @@ -33,6 +33,7 @@ "metadata": {}, "outputs": [], "source": [ + "import calendar\n", "import ee\n", "import geemap\n", "from geemap import chart" @@ -84,20 +85,8 @@ "x_property = \"label\"\n", "y_properties = [str(x).zfill(2) + \"_tmean\" for x in range(1, 13)]\n", "\n", - "labels = [\n", - " \"Jan\",\n", - " \"Feb\",\n", - " \"Mar\",\n", - " \"Apr\",\n", - " \"May\",\n", - " \"Jun\",\n", - " \"Jul\",\n", - " \"Aug\",\n", - " \"Sep\",\n", - " \"Oct\",\n", - " \"Nov\",\n", - " \"Dec\",\n", - "]\n", + "labels = calendar.month_abbr[1:] # a list of month labels, e.g. ['Jan', 'Feb', ...]\n", + "\n", "colors = [\n", " \"#604791\",\n", " \"#1d6b99\",\n", @@ -169,20 +158,7 @@ "outputs": [], "source": [ "keys = [str(x).zfill(2) + \"_ppt\" for x in range(1, 13)]\n", - "values = [\n", - " \"Jan\",\n", - " \"Feb\",\n", - " \"Mar\",\n", - " \"Apr\",\n", - " \"May\",\n", - " \"Jun\",\n", - " \"Jul\",\n", - " \"Aug\",\n", - " \"Sep\",\n", - " \"Oct\",\n", - " \"Nov\",\n", - " \"Dec\",\n", - "]" + "values = calendar.month_abbr[1:] # a list of month labels, e.g. ['Jan', 'Feb', ...]" ] }, { diff --git a/docs/notebooks/145_chart_image.ipynb b/docs/notebooks/145_chart_image.ipynb index 19dbf5b01a..be305bd4b5 100644 --- a/docs/notebooks/145_chart_image.ipynb +++ b/docs/notebooks/145_chart_image.ipynb @@ -19,11 +19,256 @@ "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": [ + "## 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": [ + "## 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": [ + "## 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" + ] } ], "metadata": { + "kernelspec": { + "display_name": "geo", + "language": "python", + "name": "python3" + }, "language_info": { - "name": "python" + "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, diff --git a/geemap/chart.py b/geemap/chart.py index 5c91b5b5a4..6a68e072ae 100644 --- a/geemap/chart.py +++ b/geemap/chart.py @@ -1669,8 +1669,8 @@ def image_regions( image, regions, stat_type=reducer, scale=scale, verbose=False, return_fc=True ) bands = image.bandNames().getInfo() - df = ee_to_df(fc)[bands + [series_property]] - feature_groups(df, series_property, bands, series_property, **kwargs) + fc = fc.select(bands + [series_property]) + return feature_by_property(fc, x_labels, series_property, **kwargs) def image_series( From 39fb4360ceca3c6feacc064837e15748b01f6c2e Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Fri, 9 Aug 2024 22:59:50 -0400 Subject: [PATCH 20/27] Add chart_image_collection.ipynb --- .../146_chart_image_collection.ipynb | 356 +++++++++++++++++- geemap/chart.py | 4 - 2 files changed, 355 insertions(+), 5 deletions(-) diff --git a/docs/notebooks/146_chart_image_collection.ipynb b/docs/notebooks/146_chart_image_collection.ipynb index 6ed10dfb79..59370dc6db 100644 --- a/docs/notebooks/146_chart_image_collection.ipynb +++ b/docs/notebooks/146_chart_image_collection.ipynb @@ -19,11 +19,365 @@ "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_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": [ + "## 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", + " xlabel=x_label,\n", + " ylabel=y_label,\n", + " colors=colors,\n", + " stroke_width=3,\n", + " legend_location=\"bottom-left\",\n", + ")\n", + "fig" + ] + }, + { + "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", + " xlabel=x_label,\n", + " ylabel=y_label,\n", + " colors=colors,\n", + " stroke_width=5,\n", + ")\n", + "fig" + ] + }, + { + "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": [ + "## 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" + ] } ], "metadata": { + "kernelspec": { + "display_name": "geo", + "language": "python", + "name": "python3" + }, "language_info": { - "name": "python" + "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, diff --git a/geemap/chart.py b/geemap/chart.py index 6a68e072ae..9c81a98216 100644 --- a/geemap/chart.py +++ b/geemap/chart.py @@ -1686,7 +1686,6 @@ def image_series( title: Optional[str] = None, x_label: Optional[str] = None, y_label: Optional[str] = None, - options: Optional[Dict[str, Any]] = None, **kwargs: Any, ) -> Chart: """ @@ -1713,7 +1712,6 @@ def image_series( empty string. y_label (Optional[str]): The label for the y-axis. Defaults to an empty string. - options (Optional[Dict[str, Any]]): Additional options for the chart. **kwargs: Additional keyword arguments to pass to the bqplot Figure or mark objects. For axes_options, see https://bqplot.github.io/bqplot/api/axes @@ -1759,7 +1757,6 @@ def get_stats(image): title, x_label, y_label, - options, **kwargs, ) return fig @@ -1808,7 +1805,6 @@ def image_series_by_region( empty string. y_label (Optional[str]): The label for the y-axis. Defaults to an empty string. - options (Optional[Dict[str, Any]]): Additional options for the chart. **kwargs: Additional keyword arguments to pass to the bqplot Figure or mark objects. For axes_options, see https://bqplot.github.io/bqplot/api/axes From 0a77c67eb4996b3089ddf89fd0ea2858140dd21d Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Sat, 10 Aug 2024 10:13:27 -0400 Subject: [PATCH 21/27] Add chart.array_values function --- .../146_chart_image_collection.ipynb | 1 - docs/notebooks/147_chart_array_list.ipynb | 132 +++++++++++++++++- geemap/chart.py | 116 ++++++++++++++- geemap/common.py | 21 +++ 4 files changed, 263 insertions(+), 7 deletions(-) diff --git a/docs/notebooks/146_chart_image_collection.ipynb b/docs/notebooks/146_chart_image_collection.ipynb index 59370dc6db..f528674f04 100644 --- a/docs/notebooks/146_chart_image_collection.ipynb +++ b/docs/notebooks/146_chart_image_collection.ipynb @@ -33,7 +33,6 @@ "metadata": {}, "outputs": [], "source": [ - "import calendar\n", "import ee\n", "import geemap\n", "from geemap import chart" diff --git a/docs/notebooks/147_chart_array_list.ipynb b/docs/notebooks/147_chart_array_list.ipynb index fc9ce8bd0d..ab3f944820 100644 --- a/docs/notebooks/147_chart_array_list.ipynb +++ b/docs/notebooks/147_chart_array_list.ipynb @@ -19,11 +19,141 @@ "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", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig.figure.save_png(\"array_values.png\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig" + ] } ], "metadata": { + "kernelspec": { + "display_name": "geo", + "language": "python", + "name": "python3" + }, "language_info": { - "name": "python" + "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, diff --git a/geemap/chart.py b/geemap/chart.py index 9c81a98216..f5f0a4e4e9 100644 --- a/geemap/chart.py +++ b/geemap/chart.py @@ -14,7 +14,7 @@ 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 +from .common import ee_to_df, zonal_stats, image_dates, hex_to_rgba from typing import List, Optional, Union, Dict, Any, Tuple @@ -108,6 +108,59 @@ def pivot_df(df: pd.DataFrame, index: str, columns: str, values: str) -> pd.Data 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 y_labels is None: + y_labels = [ + f"y{i}".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. @@ -163,6 +216,8 @@ def __init__( 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 @@ -175,17 +230,17 @@ def display(self) -> None: """ Display the chart without toolbar. """ - self._set_title_and_labels() + self._set_plt_options() display(self.figure) def _ipython_display_(self) -> None: """ Display the chart with toolbar. """ - self._set_title_and_labels() + self._set_plt_options() plt.show() - def _set_title_and_labels(self) -> None: + def _set_plt_options(self) -> None: """ Set the title and labels for the chart. """ @@ -195,6 +250,10 @@ def _set_title_and_labels(self) -> 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, @@ -333,7 +392,7 @@ def set_chart_type( else: raise ValueError("Unsupported chart type") - self._set_title_and_labels() + self._set_plt_options() def get_chart_type(self) -> Optional[str]: """ @@ -1850,3 +1909,50 @@ def image_series_by_region( **kwargs, ) return fig + + +def array_values( + array: Union[ee.Array, ee.List, List[List[float]]], + axis: int = 1, + x_labels: Optional[Union[ee.Array, ee.List, List[float]]] = None, + 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. + axis (int): The axis along which to transpose the array if needed. Defaults to 1. + x_labels (Optional[Union[ee.Array, ee.List, List[float]]]): The labels + for the x-axis. Defaults to None. + 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})" From 77640ec41e66ca4a39d07000ceecf14cff2c95c0 Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Sat, 10 Aug 2024 10:51:19 -0400 Subject: [PATCH 22/27] Add AreaChart type --- docs/notebooks/147_chart_array_list.ipynb | 80 ++++++++++++++++++++++- geemap/chart.py | 14 +++- 2 files changed, 91 insertions(+), 3 deletions(-) diff --git a/docs/notebooks/147_chart_array_list.ipynb b/docs/notebooks/147_chart_array_list.ipynb index ab3f944820..486a34cd4a 100644 --- a/docs/notebooks/147_chart_array_list.ipynb +++ b/docs/notebooks/147_chart_array_list.ipynb @@ -115,7 +115,8 @@ " y_label=\"NIR & SWIR reflectance (x1e4)\",\n", " default_size=15,\n", " xlim=(0, 800),\n", - ")" + ")\n", + "fig" ] }, { @@ -124,7 +125,8 @@ "metadata": {}, "outputs": [], "source": [ - "fig.figure.save_png(\"array_values.png\")" + "x = ee.List(pixel_vals.get(\"sur_refl_b01\"))\n", + "y = ee.List(pixel_vals.get(\"sur_refl_b06\"))" ] }, { @@ -133,6 +135,80 @@ "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": [ + " ## 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" ] } diff --git a/geemap/chart.py b/geemap/chart.py index f5f0a4e4e9..36641ed046 100644 --- a/geemap/chart.py +++ b/geemap/chart.py @@ -146,9 +146,12 @@ def array_to_df( 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{i}".zfill(len(str(len(y_values)))) for i in range(len(y_values)) + 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): @@ -350,6 +353,15 @@ def set_chart_type( colors=[color], **kwargs, ) + elif chart_type == "AreaChart": + if "fill" not in kwargs: + kwargs["fill"] = "bottom" + self.chart = plt.plot( + self.data_table[x_col], + self.data_table[y_col], + colors=[color], + **kwargs, + ) elif chart_type == "ColumnChart": self.chart = plt.bar( self.data_table[x_col], From dc55ef10a7af6c4ff89d00d1227479f047f16a4f Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Sat, 10 Aug 2024 11:14:01 -0400 Subject: [PATCH 23/27] Add more chart array examples --- docs/notebooks/147_chart_array_list.ipynb | 105 ++++++++++++++++++++++ geemap/chart.py | 4 +- 2 files changed, 107 insertions(+), 2 deletions(-) diff --git a/docs/notebooks/147_chart_array_list.ipynb b/docs/notebooks/147_chart_array_list.ipynb index 486a34cd4a..2b546210f4 100644 --- a/docs/notebooks/147_chart_array_list.ipynb +++ b/docs/notebooks/147_chart_array_list.ipynb @@ -211,6 +211,111 @@ ")\n", "fig" ] + }, + { + "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": [ + "## 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" + ] } ], "metadata": { diff --git a/geemap/chart.py b/geemap/chart.py index 36641ed046..542f79b5c3 100644 --- a/geemap/chart.py +++ b/geemap/chart.py @@ -1925,8 +1925,8 @@ def image_series_by_region( def array_values( array: Union[ee.Array, ee.List, List[List[float]]], - axis: int = 1, 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, @@ -1940,9 +1940,9 @@ def array_values( Args: array (Union[ee.Array, ee.List, List[List[float]]]): The array to convert. - axis (int): The axis along which to transpose the array if needed. Defaults to 1. 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. From e96e8c8b63d2e3631ce0c68787dcdd738666da2a Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Sat, 10 Aug 2024 18:41:05 -0400 Subject: [PATCH 24/27] Add IntervalChart --- docs/notebooks/148_chart_data_table.ipynb | 312 +++++++++++++++++++++- geemap/chart.py | 183 ++++++++----- 2 files changed, 422 insertions(+), 73 deletions(-) diff --git a/docs/notebooks/148_chart_data_table.ipynb b/docs/notebooks/148_chart_data_table.ipynb index df6bf4acb4..1c0114fc81 100644 --- a/docs/notebooks/148_chart_data_table.ipynb +++ b/docs/notebooks/148_chart_data_table.ipynb @@ -19,11 +19,321 @@ "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": [ + "## 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": [ + "## 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": [ + "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", + ")" + ] } ], "metadata": { + "kernelspec": { + "display_name": "geo", + "language": "python", + "name": "python3" + }, "language_info": { - "name": "python" + "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, diff --git a/geemap/chart.py b/geemap/chart.py index 542f79b5c3..83dbaba337 100644 --- a/geemap/chart.py +++ b/geemap/chart.py @@ -24,6 +24,8 @@ 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: """ @@ -32,14 +34,25 @@ def __init__( 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 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, @@ -331,78 +344,104 @@ def set_chart_type( "brown", ] # Default colors - 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] - - if chart_type == "ScatterChart": - self.chart = plt.scatter( - self.data_table[x_col], - self.data_table[y_col], - colors=[color], - **kwargs, - ) - elif chart_type == "LineChart": - self.chart = plt.plot( - self.data_table[x_col], - self.data_table[y_col], - colors=[color], - **kwargs, - ) - elif chart_type == "AreaChart": - if "fill" not in kwargs: - kwargs["fill"] = "bottom" - self.chart = plt.plot( - self.data_table[x_col], - self.data_table[y_col], - colors=[color], - **kwargs, - ) - elif chart_type == "ColumnChart": - self.chart = plt.bar( - self.data_table[x_col], - self.data_table[y_col], - colors=[color], - **kwargs, - ) - elif chart_type == "BarChart": - if "orientation" not in kwargs: - kwargs["orientation"] = "horizontal" - self.chart = plt.bar( - self.data_table[x_col], - self.data_table[y_col], - colors=[color], - **kwargs, - ) - elif chart_type == "AreaChart": - if "fill" not in kwargs: - kwargs["fill"] = "bottom" - self.chart = plt.plot( - self.data_table[x_col], - self.data_table[y_col], - colors=[color], - **kwargs, - ) - elif chart_type == "PieChart": - kwargs.pop("labels", None) - self.chart = plt.pie( - sizes=self.data_table[y_col], - labels=self.data_table[x_col], - colors=colors[: len(self.data_table[x_col])], - **kwargs, - ) - elif chart_type == "Table": - output = widgets.Output(**kwargs) - with output: - display(self.data_table) - output.layout = widgets.Layout(width="50%") - display(output) - else: - raise ValueError("Unsupported chart type") + 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: + raise ValueError("Unsupported chart type") self._set_plt_options() From cd080b52afa7e9f9fa2ac70fb924e319ad742fd9 Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Sat, 10 Aug 2024 18:58:26 -0400 Subject: [PATCH 25/27] Save save_png method --- docs/notebooks/148_chart_data_table.ipynb | 5 +++-- geemap/chart.py | 10 ++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/docs/notebooks/148_chart_data_table.ipynb b/docs/notebooks/148_chart_data_table.ipynb index 1c0114fc81..d469686274 100644 --- a/docs/notebooks/148_chart_data_table.ipynb +++ b/docs/notebooks/148_chart_data_table.ipynb @@ -297,7 +297,7 @@ "metadata": {}, "outputs": [], "source": [ - "chart.Chart(\n", + "fig = chart.Chart(\n", " df,\n", " chart_type=\"IntervalChart\",\n", " x_cols=\"DOY\",\n", @@ -313,7 +313,8 @@ " display_legend=True,\n", " legend_location=\"top-right\",\n", " ylim=(0, 10000),\n", - ")" + ")\n", + "fig" ] } ], diff --git a/geemap/chart.py b/geemap/chart.py index 83dbaba337..fed673afb6 100644 --- a/geemap/chart.py +++ b/geemap/chart.py @@ -249,6 +249,16 @@ def display(self) -> None: 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. From ad2e2ff32e93cdfbe94a8553646d91628ede3831 Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Sat, 10 Aug 2024 23:07:25 -0400 Subject: [PATCH 26/27] Add images to notebook --- docs/notebooks/144_chart_features.ipynb | 28 ++++++++++++ docs/notebooks/145_chart_image.ipynb | 28 ++++++++++++ .../146_chart_image_collection.ipynb | 43 +++++++++++++++++-- docs/notebooks/147_chart_array_list.ipynb | 35 +++++++++++++++ docs/notebooks/148_chart_data_table.ipynb | 21 +++++++++ geemap/chart.py | 14 ++++-- 6 files changed, 162 insertions(+), 7 deletions(-) diff --git a/docs/notebooks/144_chart_features.ipynb b/docs/notebooks/144_chart_features.ipynb index 928f65360d..b1e9f617e9 100644 --- a/docs/notebooks/144_chart_features.ipynb +++ b/docs/notebooks/144_chart_features.ipynb @@ -125,6 +125,13 @@ "fig" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/MZa99Vf.png)" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -192,6 +199,13 @@ "fig" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/6RhuUc7.png)" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -244,6 +258,13 @@ ")" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/YFZlJtc.png)" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -298,6 +319,13 @@ ")\n", "fig" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/ErIp7Oy.png)" + ] } ], "metadata": { diff --git a/docs/notebooks/145_chart_image.ipynb b/docs/notebooks/145_chart_image.ipynb index be305bd4b5..09fa2b15eb 100644 --- a/docs/notebooks/145_chart_image.ipynb +++ b/docs/notebooks/145_chart_image.ipynb @@ -97,6 +97,13 @@ "fig" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/y4rp3dK.png)" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -158,6 +165,13 @@ ")" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/5WJVCNY.png)" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -207,6 +221,13 @@ "fig" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/XqYHvBV.png)" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -250,6 +271,13 @@ ")\n", "fig" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/mY4yoYH.png)" + ] } ], "metadata": { diff --git a/docs/notebooks/146_chart_image_collection.ipynb b/docs/notebooks/146_chart_image_collection.ipynb index f528674f04..0161bd2461 100644 --- a/docs/notebooks/146_chart_image_collection.ipynb +++ b/docs/notebooks/146_chart_image_collection.ipynb @@ -109,6 +109,13 @@ "fig" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/r9zSJh6.png)" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -165,8 +172,8 @@ " x_cols=x_cols,\n", " y_cols=y_cols,\n", " title=title,\n", - " xlabel=x_label,\n", - " ylabel=y_label,\n", + " x_label=x_label,\n", + " y_label=y_label,\n", " colors=colors,\n", " stroke_width=3,\n", " legend_location=\"bottom-left\",\n", @@ -174,6 +181,13 @@ "fig" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/rnILSfI.png)" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -224,14 +238,21 @@ " scale=500,\n", " chart_type=\"LineChart\",\n", " title=title,\n", - " xlabel=x_label,\n", - " ylabel=y_label,\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": {}, @@ -296,6 +317,13 @@ "fig" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/ui6zpbl.png)" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -358,6 +386,13 @@ ")\n", "fig" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/eGqGoRs.png)" + ] } ], "metadata": { diff --git a/docs/notebooks/147_chart_array_list.ipynb b/docs/notebooks/147_chart_array_list.ipynb index 2b546210f4..5b9024c0ea 100644 --- a/docs/notebooks/147_chart_array_list.ipynb +++ b/docs/notebooks/147_chart_array_list.ipynb @@ -119,6 +119,13 @@ "fig" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/zkPlZIO.png)" + ] + }, { "cell_type": "code", "execution_count": null, @@ -150,6 +157,13 @@ "fig" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/WHUHjH6.png)" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -212,6 +226,13 @@ "fig" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/k3XRita.png)" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -271,6 +292,13 @@ "fig" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/3COY3xd.png)" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -316,6 +344,13 @@ ")\n", "fig" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/7qcxvey.png)" + ] } ], "metadata": { diff --git a/docs/notebooks/148_chart_data_table.ipynb b/docs/notebooks/148_chart_data_table.ipynb index d469686274..2f2a34dc88 100644 --- a/docs/notebooks/148_chart_data_table.ipynb +++ b/docs/notebooks/148_chart_data_table.ipynb @@ -89,6 +89,13 @@ "fig" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/vuxNmuh.png)" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -181,6 +188,13 @@ "fig" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/PWei7QC.png)" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -316,6 +330,13 @@ ")\n", "fig" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/i8ZrGPR.png)" + ] } ], "metadata": { diff --git a/geemap/chart.py b/geemap/chart.py index fed673afb6..df005e82e3 100644 --- a/geemap/chart.py +++ b/geemap/chart.py @@ -34,7 +34,7 @@ def __init__( 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 column to convert 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. @@ -182,7 +182,10 @@ class Chart: A class to create and display various types of charts from a data table. Attributes: - df (pd.DataFrame): The data to be displayed in the charts. + 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. """ @@ -451,7 +454,12 @@ def set_chart_type( output.layout = widgets.Layout(width="50%") display(output) else: - raise ValueError("Unsupported chart type") + self.chart = plt.plot( + x, + y, + colors=[color], + **kwargs, + ) self._set_plt_options() From 27c51985e967e2111f9c6ef433214cb81ba8a8fc Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Wed, 14 Aug 2024 11:07:56 -0400 Subject: [PATCH 27/27] Update mkdocs.yml --- mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index 993b50956c..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: