diff --git a/.gitignore b/.gitignore index d8bdd338..8efe899d 100644 --- a/.gitignore +++ b/.gitignore @@ -19,9 +19,4 @@ __pycache__ *.js *.png -.coverage.* -.coverage - -CLAUDE.md -*.gz -*.whl +*.ipynb diff --git a/pyleoclim/core/series.py b/pyleoclim/core/series.py index 276ece67..a7da9b64 100644 --- a/pyleoclim/core/series.py +++ b/pyleoclim/core/series.py @@ -1118,6 +1118,19 @@ def plot(self, figsize=[10, 4], fig, ax = ts.plot(color='k', savefig_settings={'path': 'ts_plot3.png'}); pyleo.closefig(fig) pyleo.savefig(fig,path='ts_plot3.png') + + Plot the CENOGRID d18O timeseries with added Geologic Time Scale annotation + + .. jupyter-execute:: + + import pyleoclim as pyleo + + ts_18 = pyleo.utils.load_dataset('cenogrid_d18O') + fig, ax = ts_18.plot(figsize=(10, 4),linewidth=0.5) + ax.invert_yaxis() # d18O is traditionally inverted + + fig, ax = pyleo.add_GTS(fig, ax, ranks=['Period', 'Epoch'], location='above') + ''' # generate default axis labels time_label, value_label = self.make_labels() diff --git a/pyleoclim/data/GTS_updated.csv b/pyleoclim/data/GTS_updated.csv new file mode 100644 index 00000000..0d014c37 --- /dev/null +++ b/pyleoclim/data/GTS_updated.csv @@ -0,0 +1,101 @@ +Period,Epoch,Stage,UpperBoundary,LowerBoundary,Average,Rperiod,Gperiod,Bperiod,Repoch,Gepoch,Bepoch,Rstage,Gstage,Bstage,Period_Abbrev,Epoch_Abbrev,Stage_Abbrev,Period_Color,Epoch_Color,Stage_Color +Quaternary,Holocene,Holocene,0.0,0.0117,0.00585,0.976470588235294,0.976470588235294,0.498039215686275,0.996078431372549,0.949019607843137,0.87843137254902,0.996078431372549,0.949019607843137,0.87843137254902,Q,H,,#f8f87f,#fef1e0,#fef1e0 +Quaternary,Pleistocene,Upper Pleistocene,0.0117,0.129,0.07035,0.976470588235294,0.976470588235294,0.498039215686275,1.0,0.949019607843137,0.682352941176471,1.0,0.949019607843137,0.827450980392157,Q,P,,#f8f87f,#fff1ae,#fff1d3 +Quaternary,Pleistocene,Chibanian,0.129,0.774,0.4515,0.976470588235294,0.976470588235294,0.498039215686275,1.0,0.949019607843137,0.682352941176471,1.0,0.949019607843137,0.780392156862745,Q,P,,#f8f87f,#fff1ae,#fff1c6 +Quaternary,Pleistocene,Calabrian,0.774,1.8,1.287,0.976470588235294,0.976470588235294,0.498039215686275,1.0,0.949019607843137,0.682352941176471,1.0,0.949019607843137,0.729411764705882,Q,P,,#f8f87f,#fff1ae,#fff1b9 +Quaternary,Pleistocene,Gelasian,1.8,2.58,2.19,0.976470588235294,0.976470588235294,0.498039215686275,1.0,0.949019607843137,0.682352941176471,1.0,0.929411764705882,0.701960784313725,Q,P,,#f8f87f,#fff1ae,#ffecb2 +Neogene,Pliocene,Piacenzian,2.58,3.6,3.09,1.0,0.901960784313726,0.0980392156862745,1.0,1.0,0.6,1.0,1.0,0.749019607843137,N,Pli,,#ffe618,#ffff99,#ffffbe +Neogene,Pliocene,Zanclean,3.6,5.33,4.465,1.0,0.901960784313726,0.0980392156862745,1.0,1.0,0.6,1.0,1.0,0.701960784313725,N,Pli,,#ffe618,#ffff99,#ffffb2 +Neogene,Miocene,Messinian,5.33,7.25,6.29,1.0,0.901960784313726,0.0980392156862745,1.0,1.0,0.0,1.0,1.0,0.450980392156863,N,M,,#ffe618,#ffff00,#ffff73 +Neogene,Miocene,Tortonian,7.25,11.63,9.44,1.0,0.901960784313726,0.0980392156862745,1.0,1.0,0.0,1.0,1.0,0.4,N,M,,#ffe618,#ffff00,#ffff66 +Neogene,Miocene,Serravallian,11.63,13.82,12.725,1.0,0.901960784313726,0.0980392156862745,1.0,1.0,0.0,1.0,1.0,0.349019607843137,N,M,,#ffe618,#ffff00,#ffff58 +Neogene,Miocene,Langhian,13.82,15.99,14.905,1.0,0.901960784313726,0.0980392156862745,1.0,1.0,0.0,1.0,1.0,0.301960784313725,N,M,,#ffe618,#ffff00,#ffff4c +Neogene,Miocene,Burdigalian,15.99,20.45,18.22,1.0,0.901960784313726,0.0980392156862745,1.0,1.0,0.0,1.0,1.0,0.254901960784314,N,M,,#ffe618,#ffff00,#ffff41 +Neogene,Miocene,Aquitanian,20.45,23.04,21.745,1.0,0.901960784313726,0.0980392156862745,1.0,1.0,0.0,1.0,1.0,0.2,N,M,,#ffe618,#ffff00,#ffff33 +Paleogene,Oligocene,Chattian,23.04,27.29,25.165,0.992156862745098,0.603921568627451,0.32156862745098,0.992156862745098,0.752941176470588,0.47843137254902,0.996078431372549,0.901960784313726,0.666666666666667,Pg,O,,#fc9a51,#fcbf7a,#fee6aa +Paleogene,Oligocene,Rupelian,27.29,33.9,30.595,0.992156862745098,0.603921568627451,0.32156862745098,0.992156862745098,0.752941176470588,0.47843137254902,0.996078431372549,0.850980392156863,0.603921568627451,Pg,O,,#fc9a51,#fcbf7a,#fed99a +Paleogene,Eocene,Priabonian,33.9,37.71,35.805,0.992156862745098,0.603921568627451,0.32156862745098,0.992156862745098,0.705882352941177,0.423529411764706,0.992156862745098,0.803921568627451,0.631372549019608,Pg,E,,#fc9a51,#fcb46c,#fccda1 +Paleogene,Eocene,Bartonian,37.71,41.03,39.37,0.992156862745098,0.603921568627451,0.32156862745098,0.992156862745098,0.705882352941177,0.423529411764706,0.992156862745098,0.752941176470588,0.568627450980392,Pg,E,,#fc9a51,#fcb46c,#fcbf90 +Paleogene,Eocene,Lutetian,41.03,48.07,44.55,0.992156862745098,0.603921568627451,0.32156862745098,0.992156862745098,0.705882352941177,0.423529411764706,0.988235294117647,0.705882352941177,0.509803921568627,Pg,E,,#fc9a51,#fcb46c,#fbb481 +Paleogene,Eocene,Ypresian,48.07,56.0,52.035,0.992156862745098,0.603921568627451,0.32156862745098,0.992156862745098,0.705882352941177,0.423529411764706,0.988235294117647,0.654901960784314,0.450980392156863,Pg,E,,#fc9a51,#fcb46c,#fba773 +Paleogene,Paleocene,Thanetian,56.0,59.24,57.62,0.992156862745098,0.603921568627451,0.32156862745098,0.992156862745098,0.654901960784314,0.372549019607843,0.992156862745098,0.749019607843137,0.435294117647059,Pg,P,,#fc9a51,#fca75e,#fcbe6f +Paleogene,Paleocene,Selandian,59.24,61.66,60.45,0.992156862745098,0.603921568627451,0.32156862745098,0.992156862745098,0.654901960784314,0.372549019607843,0.996078431372549,0.749019607843137,0.396078431372549,Pg,P,,#fc9a51,#fca75e,#febe65 +Paleogene,Paleocene,Danian,61.66,66.0,63.83,0.992156862745098,0.603921568627451,0.32156862745098,0.992156862745098,0.654901960784314,0.372549019607843,0.992156862745098,0.705882352941177,0.384313725490196,Pg,P,,#fc9a51,#fca75e,#fcb461 +Cretaceous,Upper,Maastrichtian,66.0,72.17,69.085,0.498039215686275,0.776470588235294,0.305882352941176,0.650980392156863,0.847058823529412,0.290196078431373,0.949019607843137,0.980392156862745,0.549019607843137,K,,M,#7fc54d,#a6d84a,#f1f98b +Cretaceous,Upper,Campanian,72.17,83.65,77.91,0.498039215686275,0.776470588235294,0.305882352941176,0.650980392156863,0.847058823529412,0.290196078431373,0.901960784313726,0.956862745098039,0.498039215686275,K,,C,#7fc54d,#a6d84a,#e6f37f +Cretaceous,Upper,Santonian,83.65,85.7,84.675,0.498039215686275,0.776470588235294,0.305882352941176,0.650980392156863,0.847058823529412,0.290196078431373,0.850980392156863,0.937254901960784,0.454901960784314,K,,S,#7fc54d,#a6d84a,#d9ee74 +Cretaceous,Upper,Coniacian,85.7,89.39,87.545,0.498039215686275,0.776470588235294,0.305882352941176,0.650980392156863,0.847058823529412,0.290196078431373,0.8,0.913725490196078,0.407843137254902,K,,Cn,#7fc54d,#a6d84a,#cce868 +Cretaceous,Upper,Turonian,89.39,93.9,91.645,0.498039215686275,0.776470588235294,0.305882352941176,0.650980392156863,0.847058823529412,0.290196078431373,0.749019607843137,0.890196078431372,0.364705882352941,K,,T,#7fc54d,#a6d84a,#bee25c +Cretaceous,Upper,Cenomanian,93.9,100.5,97.2,0.498039215686275,0.776470588235294,0.305882352941176,0.650980392156863,0.847058823529412,0.290196078431373,0.701960784313725,0.870588235294118,0.325490196078431,K,,C,#7fc54d,#a6d84a,#b2de52 +Cretaceous,Lower,Albian,100.5,113.2,106.85,0.498039215686275,0.776470588235294,0.305882352941176,0.549019607843137,0.803921568627451,0.341176470588235,0.8,0.917647058823529,0.592156862745098,K,,A,#7fc54d,#8bcd56,#cce997 +Cretaceous,Lower,Aptian,113.2,121.4,117.3,0.498039215686275,0.776470588235294,0.305882352941176,0.549019607843137,0.803921568627451,0.341176470588235,0.749019607843137,0.894117647058824,0.541176470588235,K,,Ap,#7fc54d,#8bcd56,#bee489 +Cretaceous,Lower,Barremian,121.4,126.5,123.95,0.498039215686275,0.776470588235294,0.305882352941176,0.549019607843137,0.803921568627451,0.341176470588235,0.701960784313725,0.874509803921569,0.498039215686275,K,,B,#7fc54d,#8bcd56,#b2df7f +Cretaceous,Lower,Hauterivian,126.5,132.6,129.55,0.498039215686275,0.776470588235294,0.305882352941176,0.549019607843137,0.803921568627451,0.341176470588235,0.650980392156863,0.850980392156863,0.458823529411765,K,,H,#7fc54d,#8bcd56,#a6d975 +Cretaceous,Lower,Valanginian,132.6,137.7,135.15,0.498039215686275,0.776470588235294,0.305882352941176,0.549019607843137,0.803921568627451,0.341176470588235,0.6,0.827450980392157,0.415686274509804,K,,V,#7fc54d,#8bcd56,#99d36a +Cretaceous,Lower,Berriasian,137.7,143.1,140.4,0.498039215686275,0.776470588235294,0.305882352941176,0.549019607843137,0.803921568627451,0.341176470588235,0.549019607843137,0.803921568627451,0.376470588235294,K,,B,#7fc54d,#8bcd56,#8bcd5f +Jurassic,Upper,Tithonian,143.1,149.24,146.17,0.203921568627451,0.698039215686274,0.788235294117647,0.701960784313725,0.890196078431372,0.933333333333333,0.850980392156863,0.945098039215686,0.968627450980392,J,,,#34b1c9,#b2e2ed,#d9f0f6 +Jurassic,Upper,Kimmeridgian,149.24,154.78,152.01,0.203921568627451,0.698039215686274,0.788235294117647,0.701960784313725,0.890196078431372,0.933333333333333,0.8,0.925490196078431,0.956862745098039,J,,,#34b1c9,#b2e2ed,#ccebf3 +Jurassic,Upper,Oxfordian,154.78,161.53,158.155,0.203921568627451,0.698039215686274,0.788235294117647,0.701960784313725,0.890196078431372,0.933333333333333,0.749019607843137,0.905882352941176,0.945098039215686,J,,,#34b1c9,#b2e2ed,#bee6f0 +Jurassic,Middle,Callovian,161.53,165.29,163.41,0.203921568627451,0.698039215686274,0.788235294117647,0.501960784313725,0.811764705882353,0.847058823529412,0.749019607843137,0.905882352941176,0.898039215686275,J,,,#34b1c9,#7fcfd8,#bee6e5 +Jurassic,Middle,Bathonian,165.29,168.17,166.73,0.203921568627451,0.698039215686274,0.788235294117647,0.501960784313725,0.811764705882353,0.847058823529412,0.701960784313725,0.886274509803922,0.890196078431372,J,,,#34b1c9,#7fcfd8,#b2e2e2 +Jurassic,Middle,Bajocian,168.17,170.9,169.535,0.203921568627451,0.698039215686274,0.788235294117647,0.501960784313725,0.811764705882353,0.847058823529412,0.650980392156863,0.866666666666667,0.87843137254902,J,,,#34b1c9,#7fcfd8,#a6dde0 +Jurassic,Middle,Aalenian,170.9,174.7,172.8,0.203921568627451,0.698039215686274,0.788235294117647,0.501960784313725,0.811764705882353,0.847058823529412,0.603921568627451,0.850980392156863,0.866666666666667,J,,,#34b1c9,#7fcfd8,#9ad9dd +Jurassic,Lower,Toarcian,174.7,184.2,179.45,0.203921568627451,0.698039215686274,0.788235294117647,0.258823529411765,0.682352941176471,0.815686274509804,0.6,0.807843137254902,0.890196078431372,J,,,#34b1c9,#42aed0,#99cee2 +Jurassic,Lower,Pliensbachian,184.2,192.9,188.55,0.203921568627451,0.698039215686274,0.788235294117647,0.258823529411765,0.682352941176471,0.815686274509804,0.501960784313725,0.772549019607843,0.866666666666667,J,,,#34b1c9,#42aed0,#7fc4dd +Jurassic,Lower,Sinemurian,192.9,199.46,196.18,0.203921568627451,0.698039215686274,0.788235294117647,0.258823529411765,0.682352941176471,0.815686274509804,0.403921568627451,0.737254901960784,0.847058823529412,J,,,#34b1c9,#42aed0,#67bbd8 +Jurassic,Lower,Hettangian,199.46,201.36,200.41,0.203921568627451,0.698039215686274,0.788235294117647,0.258823529411765,0.682352941176471,0.815686274509804,0.305882352941176,0.701960784313725,0.827450980392157,J,,,#34b1c9,#42aed0,#4db2d3 +Triassic,Upper,Rhaetian,201.36,205.74,203.55,0.505882352941176,0.168627450980392,0.572549019607843,0.741176470588235,0.549019607843137,0.764705882352941,0.890196078431372,0.725490196078431,0.858823529411765,T,,,#802a91,#bc8bc2,#e2b8db +Triassic,Upper,Norian,205.74,227.3,216.52,0.505882352941176,0.168627450980392,0.572549019607843,0.741176470588235,0.549019607843137,0.764705882352941,0.83921568627451,0.666666666666667,0.827450980392157,T,,,#802a91,#bc8bc2,#d6aad3 +Triassic,Upper,Carnian,227.3,237.0,232.15,0.505882352941176,0.168627450980392,0.572549019607843,0.741176470588235,0.549019607843137,0.764705882352941,0.788235294117647,0.607843137254902,0.796078431372549,T,,,#802a91,#bc8bc2,#c99bcb +Triassic,Middle,Ladinian,237.0,241.46,239.23,0.505882352941176,0.168627450980392,0.572549019607843,0.694117647058824,0.407843137254902,0.694117647058824,0.788235294117647,0.513725490196078,0.749019607843137,T,,,#802a91,#b168b1,#c982be +Triassic,Middle,Anisian,241.46,246.7,244.08,0.505882352941176,0.168627450980392,0.572549019607843,0.694117647058824,0.407843137254902,0.694117647058824,0.737254901960784,0.458823529411765,0.717647058823529,T,,,#802a91,#b168b1,#bb75b6 +Triassic,Lower,Olenekian,246.7,249.88,248.29,0.505882352941176,0.168627450980392,0.572549019607843,0.596078431372549,0.223529411764706,0.6,0.690196078431373,0.317647058823529,0.647058823529412,T,,,#802a91,#983999,#b050a5 +Triassic,Lower,Induan,249.88,251.9,250.89,0.505882352941176,0.168627450980392,0.572549019607843,0.596078431372549,0.223529411764706,0.6,0.643137254901961,0.274509803921569,0.623529411764706,T,,,#802a91,#983999,#a4469f +Permian,Lopingian,Changhsingian,251.9,254.24,253.07,0.941176470588235,0.250980392156863,0.156862745098039,0.843137254901961,0.654901960784314,0.580392156862745,0.988235294117647,0.752941176470588,0.698039215686274,P,,,#ef4027,#d7a793,#fbbfb1 +Permian,Lopingian,Wuchiapingian,254.24,259.55,256.895,0.941176470588235,0.250980392156863,0.156862745098039,0.843137254901961,0.654901960784314,0.580392156862745,0.988235294117647,0.705882352941177,0.635294117647059,P,,,#ef4027,#d7a793,#fbb4a2 +Permian,Guadalupian,Capitanian,259.55,264.34,261.945,0.941176470588235,0.250980392156863,0.156862745098039,0.984313725490196,0.454901960784314,0.36078431372549,0.984313725490196,0.603921568627451,0.52156862745098,P,,,#ef4027,#fb745b,#fb9a84 +Permian,Guadalupian,Wordian,264.34,269.21,266.775,0.941176470588235,0.250980392156863,0.156862745098039,0.984313725490196,0.454901960784314,0.36078431372549,0.984313725490196,0.552941176470588,0.462745098039216,P,,,#ef4027,#fb745b,#fb8c76 +Permian,Guadalupian,Roadian,269.21,274.37,271.79,0.941176470588235,0.250980392156863,0.156862745098039,0.984313725490196,0.454901960784314,0.36078431372549,0.984313725490196,0.501960784313725,0.411764705882353,P,,,#ef4027,#fb745b,#fb7f69 +Permian,Cisuralian,Kungurian,274.37,283.3,278.835,0.941176470588235,0.250980392156863,0.156862745098039,0.937254901960784,0.345098039215686,0.270588235294118,0.890196078431372,0.529411764705882,0.462745098039216,P,,,#ef4027,#ee5745,#e28676 +Permian,Cisuralian,Artinskian,283.3,290.51,286.905,0.941176470588235,0.250980392156863,0.156862745098039,0.937254901960784,0.345098039215686,0.270588235294118,0.890196078431372,0.482352941176471,0.407843137254902,P,,,#ef4027,#ee5745,#e27b68 +Permian,Cisuralian,Sakmarian,290.51,293.52,292.015,0.941176470588235,0.250980392156863,0.156862745098039,0.937254901960784,0.345098039215686,0.270588235294118,0.890196078431372,0.435294117647059,0.36078431372549,P,,,#ef4027,#ee5745,#e26f5b +Permian,Cisuralian,Asselian,293.52,298.89,296.205,0.941176470588235,0.250980392156863,0.156862745098039,0.937254901960784,0.345098039215686,0.270588235294118,0.890196078431372,0.388235294117647,0.313725490196078,P,,,#ef4027,#ee5745,#e2624f +Carboniferous,Upper,Gzhelian,298.89,303.68,301.285,0.403921568627451,0.647058823529412,0.6,0.749019607843137,0.815686274509804,0.729411764705882,0.8,0.831372549019608,0.780392156862745,C,,,#67a599,#bed0b9,#ccd4c6 +Carboniferous,Upper,Kasimovian,303.68,307.02,305.35,0.403921568627451,0.647058823529412,0.6,0.749019607843137,0.815686274509804,0.729411764705882,0.749019607843137,0.815686274509804,0.772549019607843,C,,,#67a599,#bed0b9,#bed0c4 +Carboniferous,Middle,Moscovian,307.02,315.15,311.085,0.403921568627451,0.647058823529412,0.6,0.650980392156863,0.780392156862745,0.717647058823529,0.701960784313725,0.796078431372549,0.725490196078431,C,,,#67a599,#a6c6b6,#b2cbb8 +Carboniferous,Lower,Bashkirian,315.15,323.4,319.275,0.403921568627451,0.647058823529412,0.6,0.549019607843137,0.745098039215686,0.705882352941177,0.6,0.76078431372549,0.709803921568627,C,,,#67a599,#8bbdb4,#99c1b4 +Carboniferous,Upper,Serpukhovian,323.4,330.34,326.87,0.403921568627451,0.647058823529412,0.6,0.701960784313725,0.745098039215686,0.423529411764706,0.749019607843137,0.76078431372549,0.419607843137255,C,,,#67a599,#b2bd6c,#bec16b +Carboniferous,Middle,Visean,330.34,346.73,338.535,0.403921568627451,0.647058823529412,0.6,0.6,0.705882352941177,0.423529411764706,0.650980392156863,0.725490196078431,0.423529411764706,C,,,#67a599,#99b46c,#a6b86c +Carboniferous,Lower,Tournaisian,346.73,359.3,353.015,0.403921568627451,0.647058823529412,0.6,0.501960784313725,0.670588235294118,0.423529411764706,0.549019607843137,0.690196078431373,0.423529411764706,C,,,#67a599,#7fab6c,#8bb06c +Devonian,Upper,Famennian,359.3,371.1,365.2,0.796078431372549,0.549019607843137,0.215686274509804,0.945098039215686,1.0,0.615686274509804,0.949019607843137,0.929411764705882,0.772549019607843,D,,,#cb8b37,#f0ff9d,#f1ecc4 +Devonian,Upper,Frasnian,371.1,378.9,375.0,0.796078431372549,0.549019607843137,0.215686274509804,0.945098039215686,1.0,0.615686274509804,0.949019607843137,0.929411764705882,0.67843137254902,D,,,#cb8b37,#f0ff9d,#f1ecad +Devonian,Middle,Givetian,378.9,385.3,382.1,0.796078431372549,0.549019607843137,0.215686274509804,0.945098039215686,0.784313725490196,0.407843137254902,0.945098039215686,0.882352941176471,0.52156862745098,D,,,#cb8b37,#f0c768,#f0e184 +Devonian,Middle,Eifelian,385.3,394.3,389.8,0.796078431372549,0.549019607843137,0.215686274509804,0.945098039215686,0.784313725490196,0.407843137254902,0.945098039215686,0.835294117647059,0.462745098039216,D,,,#cb8b37,#f0c768,#f0d576 +Devonian,Lower,Emsian,394.3,410.5,402.4,0.796078431372549,0.549019607843137,0.215686274509804,0.898039215686275,0.674509803921569,0.301960784313725,0.898039215686275,0.815686274509804,0.458823529411765,D,,,#cb8b37,#e5ac4c,#e5d075 +Devonian,Lower,Pragian,410.5,412.4,411.45,0.796078431372549,0.549019607843137,0.215686274509804,0.898039215686275,0.674509803921569,0.301960784313725,0.898039215686275,0.768627450980392,0.407843137254902,D,,,#cb8b37,#e5ac4c,#e5c368 +Devonian,Lower,Lochkovian,412.4,419.0,415.7,0.796078431372549,0.549019607843137,0.215686274509804,0.898039215686275,0.674509803921569,0.301960784313725,0.898039215686275,0.717647058823529,0.352941176470588,D,,,#cb8b37,#e5ac4c,#e5b659 +Silurian,Pridoli,Pridoli,419.0,422.73,420.865,0.701960784313725,0.882352941176471,0.713725490196078,0.901960784313726,0.96078431372549,0.882352941176471,0.901960784313726,0.96078431372549,0.882352941176471,S,,,#b2e1b5,#e6f4e1,#e6f4e1 +Silurian,Ludlow,Ludfordian,422.73,425.01,423.87,0.701960784313725,0.882352941176471,0.713725490196078,0.749019607843137,0.901960784313726,0.811764705882353,0.850980392156863,0.941176470588235,0.874509803921569,S,,,#b2e1b5,#bee6cf,#d9efdf +Silurian,Ludlow,Gorstian,425.01,426.74,425.875,0.701960784313725,0.882352941176471,0.713725490196078,0.749019607843137,0.901960784313726,0.811764705882353,0.8,0.925490196078431,0.866666666666667,S,,,#b2e1b5,#bee6cf,#ccebdd +Silurian,Wenlock,Homerian,426.74,430.62,428.68,0.701960784313725,0.882352941176471,0.713725490196078,0.701960784313725,0.882352941176471,0.76078431372549,0.8,0.92156862745098,0.819607843137255,S,,,#b2e1b5,#b2e1c1,#ccead1 +Silurian,Wenlock,Sheinwoodian,430.62,432.93,431.775,0.701960784313725,0.882352941176471,0.713725490196078,0.701960784313725,0.882352941176471,0.76078431372549,0.749019607843137,0.901960784313726,0.764705882352941,S,,,#b2e1b5,#b2e1c1,#bee6c2 +Silurian,Llandovery,Telychian,432.93,438.59,435.76,0.701960784313725,0.882352941176471,0.713725490196078,0.6,0.843137254901961,0.701960784313725,0.749019607843137,0.901960784313726,0.811764705882353,S,,,#b2e1b5,#99d7b2,#bee6cf +Silurian,Llandovery,Aeronian,438.59,440.49,439.54,0.701960784313725,0.882352941176471,0.713725490196078,0.6,0.843137254901961,0.701960784313725,0.701960784313725,0.882352941176471,0.76078431372549,S,,,#b2e1b5,#99d7b2,#b2e1c1 +Silurian,Llandovery,Rhuddanian,440.49,443.07,441.78,0.701960784313725,0.882352941176471,0.713725490196078,0.6,0.843137254901961,0.701960784313725,0.650980392156863,0.862745098039216,0.709803921568627,S,,,#b2e1b5,#99d7b2,#a6dcb4 +Ordovician,Upper,Hirnantian,443.07,445.21,444.14,0.0,0.572549019607843,0.43921568627451,0.498039215686275,0.792156862745098,0.576470588235294,0.650980392156863,0.858823529411765,0.670588235294118,O,,,#009170,#7fca92,#a6dbab +Ordovician,Upper,Katian,445.21,452.75,448.98,0.0,0.572549019607843,0.43921568627451,0.498039215686275,0.792156862745098,0.576470588235294,0.6,0.83921568627451,0.623529411764706,O,,,#009170,#7fca92,#99d69f +Ordovician,Upper,Sandbian,452.75,458.18,455.465,0.0,0.572549019607843,0.43921568627451,0.498039215686275,0.792156862745098,0.576470588235294,0.549019607843137,0.815686274509804,0.580392156862745,O,,,#009170,#7fca92,#8bd093 +Ordovician,Middle,Darriwilian,458.18,469.42,463.8,0.0,0.572549019607843,0.43921568627451,0.301960784313725,0.705882352941177,0.494117647058824,0.454901960784314,0.776470588235294,0.611764705882353,O,,,#009170,#4cb47e,#74c59c +Ordovician,Middle,Dapingian,469.42,471.26,470.34,0.0,0.572549019607843,0.43921568627451,0.301960784313725,0.705882352941177,0.494117647058824,0.4,0.752941176470588,0.572549019607843,O,,,#009170,#4cb47e,#66bf91 +Ordovician,Lower,Floian,471.26,477.08,474.17,0.0,0.572549019607843,0.43921568627451,0.101960784313725,0.615686274509804,0.435294117647059,0.254901960784314,0.690196078431373,0.529411764705882,O,,,#009170,#199d6f,#41b086 +Ordovician,Lower,Tremadocian,477.08,486.85,481.965,0.0,0.572549019607843,0.43921568627451,0.101960784313725,0.615686274509804,0.435294117647059,0.2,0.662745098039216,0.494117647058824,O,,,#009170,#199d6f,#33a97e +Cambrian,Furongian,Stage 10,486.85,491.0,488.925,0.498039215686275,0.627450980392157,0.337254901960784,0.701960784313725,0.87843137254902,0.584313725490196,0.901960784313726,0.96078431372549,0.788235294117647,Cm,,,#7fa055,#b2e094,#e6f4c9 +Cambrian,Furongian,Jiangshanian,491.0,494.2,492.6,0.498039215686275,0.627450980392157,0.337254901960784,0.701960784313725,0.87843137254902,0.584313725490196,0.850980392156863,0.941176470588235,0.733333333333333,Cm,,,#7fa055,#b2e094,#d9efba +Cambrian,Furongian,Paibian,494.2,497.0,495.6,0.498039215686275,0.627450980392157,0.337254901960784,0.701960784313725,0.87843137254902,0.584313725490196,0.8,0.92156862745098,0.682352941176471,Cm,,,#7fa055,#b2e094,#cceaae +Cambrian,Miaolingian,Guzhangian,497.0,500.5,498.75,0.498039215686275,0.627450980392157,0.337254901960784,0.650980392156863,0.811764705882353,0.525490196078431,0.8,0.874509803921569,0.666666666666667,Cm,,,#7fa055,#a6cf85,#ccdfaa +Cambrian,Miaolingian,Drumian,500.5,504.5,502.5,0.498039215686275,0.627450980392157,0.337254901960784,0.650980392156863,0.811764705882353,0.525490196078431,0.749019607843137,0.850980392156863,0.615686274509804,Cm,,,#7fa055,#a6cf85,#bed99d +Cambrian,Miaolingian,Stage 5,504.5,509.0,506.75,0.498039215686275,0.627450980392157,0.337254901960784,0.650980392156863,0.811764705882353,0.525490196078431,0.701960784313725,0.831372549019608,0.572549019607843,Cm,,,#7fa055,#a6cf85,#b2d491 +Cambrian,Series 2,Stage 4,509.0,514.5,511.75,0.498039215686275,0.627450980392157,0.337254901960784,0.6,0.752941176470588,0.470588235294118,0.701960784313725,0.792156862745098,0.556862745098039,Cm,,,#7fa055,#99bf78,#b2ca8d +Cambrian,Series 2,Stage 3,514.5,521.0,517.75,0.498039215686275,0.627450980392157,0.337254901960784,0.6,0.752941176470588,0.470588235294118,0.650980392156863,0.772549019607843,0.513725490196078,Cm,,,#7fa055,#99bf78,#a6c482 +Cambrian,Terreneuvian,Stage 2,521.0,529.0,525.0,0.498039215686275,0.627450980392157,0.337254901960784,0.549019607843137,0.690196078431373,0.423529411764706,0.650980392156863,0.729411764705882,0.501960784313725,Cm,,,#7fa055,#8bb06c,#a6b97f +Cambrian,Terreneuvian,Fortunian,529.0,538.8,533.9,0.498039215686275,0.627450980392157,0.337254901960784,0.549019607843137,0.690196078431373,0.423529411764706,0.6,0.709803921568627,0.458823529411765,Cm,,,#7fa055,#8bb06c,#99b475 diff --git a/pyleoclim/data/i-c-stratigraphy_2024_11_24.csv b/pyleoclim/data/i-c-stratigraphy_2024_11_24.csv new file mode 100644 index 00000000..aab00ec9 --- /dev/null +++ b/pyleoclim/data/i-c-stratigraphy_2024_11_24.csv @@ -0,0 +1,190 @@ +# International Chronostratigraphic Chart (ICS) +# Constructed from the Turtle file at: https://github.com/i-c-stratigraphy/chart/blob/main/chart.ttl +# Date of last update of Turtle file: 2024-11-24 +# Source: International Commission on Stratigraphy (ICS), chart.ttl (SKOS/RDF ontology) +# +# Notes: +# - Units: All boundary values are in millions of years ago (Ma). +# - Columns included: 'Type', 'Name', 'Abbrev', 'Color', 'UpperBoundary', 'LowerBoundary'. +# - Colors: official ICS chart hexadecimal colors. +# - Abbreviations: from ICS skos:notation or custom overrides. +# +Rank,Name,Abbrev,Color,UpperBoundary,LowerBoundary +Eon,Phanerozoic,PH,#9AD9DD,0.0,538.8 +Eon,Proterozoic,PR,#F73563,538.8,2500.0 +Eon,Archean,AR,#F0047F,2500.0,4031.0 +Eon,Hadean,HA,#AE027E,4031.0,4567.0 +Era,Cenozoic,CZ,#F2F91D,0.0,66.0 +Era,Mesozoic,MZ,#67C5CA,66.0,251.902 +Era,Paleozoic,PZ,#99C08D,251.902,538.8 +Era,Neoproterozoic,NP,#FEB342,538.8,1000.0 +Era,Mesoproterozoic,MP,#FDB462,1000.0,1600.0 +Era,Paleoproterozoic,PP,#F74370,1600.0,2500.0 +Era,Neoarchean,NA,#F99BC1,2500.0,2800.0 +Era,Mesoarchean,MA,#F768A9,2800.0,3200.0 +Era,Paleoarchean,PA,#F4449F,3200.0,3600.0 +Era,Eoarchean,EA,#DA037F,3600.0,4031.0 +Period,Quaternary,Q,#F9F97F,0.0,2.58 +Period,Neogene,N,#FFE619,2.58,23.04 +Period,Paleogene,E,#FD9A52,23.04,66.0 +Period,Cretaceous,K,#7FC64E,66.0,143.1 +Period,Jurassic,J,#34B2C9,143.1,201.4 +Period,Triassic,T,#812B92,201.4,251.902 +Period,Permian,P,#F04028,251.902,298.9 +Period,Carboniferous,C,#67A599,298.9,358.86 +Period,Devonian,D,#CB8C37,358.86,419.62 +Period,Silurian,S,#B3E1B6,419.62,443.1 +Period,Ordovician,O,#009270,443.1,486.85 +Period,Cambrian,Ep,#7FA056,486.85,538.8 +Period,Ediacaran,NP3,#FED96A,538.8,635.0 +Period,Cryogenian,NP2,#FECC5C,635.0,720.0 +Period,Tonian,NP1,#FEBF4E,720.0,1000.0 +Period,Stenian,MP3,#FED99A,1000.0,1200.0 +Period,Ectasian,MP2,#FDCC8A,1200.0,1400.0 +Period,Calymmian,MP1,#FDC07A,1400.0,1600.0 +Period,Statherian,PP4,#F875A7,1600.0,1800.0 +Period,Orosirian,PP3,#F76898,1800.0,2050.0 +Period,Rhyacian,PP2,#F75B89,2050.0,2300.0 +Period,Siderian,PP1,#F74F7C,2300.0,2500.0 +Epoch,Holocene,Q2,#FEEBD2,0.0,0.0117 +Epoch,Pleistocene,Q1,#FFEFAF,0.0117,2.58 +Epoch,Pliocene,N2,#FFFF99,2.58,5.333 +Epoch,Miocene,N1,#FFFF00,5.333,23.04 +Epoch,Oligocene,E3,#FEC07A,23.04,33.9 +Epoch,Eocene,E2,#FDB46C,33.9,56.0 +Epoch,Paleocene,E1,#FDA75F,56.0,66.0 +Epoch,Late,K2,#A6D84A,66.0,100.5 +Epoch,Early,K1,#8CCD57,100.5,143.1 +Epoch,Late Jurassic,J3,#B3E3EE,143.1,161.5 +Epoch,Middle Jurassic,J2,#80CFD8,161.5,174.7 +Epoch,Early Jurassic,J1,#42AED0,174.7,201.4 +Epoch,Late Triassic,T3,#BD8CC3,201.4,237.0 +Epoch,Middle Triassic,T2,#B168B1,237.0,246.7 +Epoch,Early Triassic,T1,#983999,246.7,251.902 +Epoch,Lopingian,P3,#FBA794,251.902,259.51 +Epoch,Guadalupian,P2,#FB745C,259.51,274.4 +Epoch,Cisuralian,P1,#EF5845,274.4,298.9 +Epoch,Late Pennsylvanian,C2c6c7,#BFD0BA,298.9,307.0 +Epoch,Middle Pennsylvanian,C2c5,#A6C7B7,307.0,315.2 +Epoch,Early Pennsylvanian,C2c4,#8CBEB4,315.2,323.4 +Epoch,Late Mississippian,C1c3,#B3BE6C,323.4,330.3 +Epoch,Middle Mississippian,C1c2,#99B46C,330.3,346.7 +Epoch,Early Mississippian,C1c1,#80AB6C,346.7,358.86 +Epoch,Late Devonian,D3,#F1E19D,358.86,382.31 +Epoch,Middle Devonian,D2,#F1C868,382.31,393.47 +Epoch,Early Devonian,D1,#E5AC4D,393.47,419.62 +Epoch,Ludlow,S3,#BFE6CF,419.62,426.7 +Epoch,Wenlock,S2,#B3E1C2,426.7,432.9 +Epoch,Llandovery,S1,#99D7B3,432.9,443.1 +Epoch,Late Ordovician,O3,#7FCA93,443.1,458.2 +Epoch,Middle Ordovician,O2,#4DB47E,458.2,471.3 +Epoch,Early Ordovician,O1,#1A9D6F,471.3,486.85 +Epoch,Furongian,Ep4,#B3E095,486.85,497.0 +Epoch,Miaolingian,Ep3,#A6CF86,497.0,506.5 +Epoch,Series 2,Ep2,#99C078,506.5,521.0 +Epoch,Terreneuvian,Ep1,#8CB06C,521.0,538.8 +Stage,Meghalayan,q7,#FDEDEC,0.0,0.0042 +Stage,Northgrippian,q6,#FDECE4,0.0042,0.0082 +Stage,Greenlandian,q5,#FEECDB,0.0082,0.0117 +Stage,Late,q4,#FFF2D3,0.0117,0.129 +Stage,Chibanian,q3,#FFF2C7,0.129,0.774 +Stage,Calabrian,q2,#FFF2BA,0.774,1.8 +Stage,Gelasian,q1,#FFEDB3,1.8,2.58 +Stage,Piacenzian,n8,#FFFFBF,2.58,3.6 +Stage,Zanclean,n7,#FFFFB3,3.6,5.333 +Stage,Messinian,n6,#FFFF73,5.333,7.246 +Stage,Tortonian,n5,#FFFF66,7.246,11.63 +Stage,Serravallian,n4,#FFFF59,11.63,13.82 +Stage,Langhian,n3,#FFFF4D,13.82,15.98 +Stage,Burdigalian,n2,#FFFF41,15.98,20.45 +Stage,Aquitanian,n1,#FFFF33,20.45,23.03 +Stage,Chattian,e9,#FEE6AA,23.04,27.3 +Stage,Rupelian,e8,#FED99A,27.3,33.9 +Stage,Priabonian,e7,#FDCDA1,33.9,37.71 +Stage,Bartonian,e6,#FDC091,37.71,41.03 +Stage,Lutetian,e5,#FDB482,41.03,48.07 +Stage,Ypresian,e4,#FCA773,48.07,56.0 +Stage,Thanetian,e3,#FDBF6F,56.0,59.24 +Stage,Selandian,e2,#FEBF65,59.24,61.66 +Stage,Danian,e1,#FDB462,61.66,66.0 +Stage,Maastrichtian,k6,#F2FA8C,66.0,72.2 +Stage,Campanian,k5,#E6F47F,72.2,83.6 +Stage,Santonian,k4,#D9EF74,83.6,85.7 +Stage,Coniacian,k3,#CCE968,85.7,89.8 +Stage,Turonian,k2,#BFE35D,89.8,93.9 +Stage,Cenomanian,k1,#B3DE53,93.9,100.5 +Stage,Albian,b6,#CCEA97,100.5,113.2 +Stage,Aptian,b5,#BFE48A,113.2,121.4 +Stage,Barremian,b4,#B3DF7F,121.4,125.77 +Stage,Hauterivian,b3,#A6D975,125.77,132.6 +Stage,Valanginian,b2,#99D36A,132.6,137.05 +Stage,Berriasian,b1,#8CCD60,137.05,143.1 +Stage,Tithonian,j7,#D9F1F7,143.1,149.2 +Stage,Kimmeridgian,j6,#CCECF4,149.2,154.8 +Stage,Oxfordian,j5,#BFE7F1,154.8,161.5 +Stage,Callovian,j4,#BFE7E5,161.5,165.3 +Stage,Bathonian,j3,#B3E2E3,165.3,168.2 +Stage,Bajocian,j2,#A6DDE0,168.2,170.9 +Stage,Aalenian,j1,#9AD9DD,170.9,174.7 +Stage,Toarcian,I4,#99CEE3,174.7,184.2 +Stage,Pliensbachian,I3,#80C5DD,184.2,192.9 +Stage,Sinemurian,I2,#67BCD8,192.9,199.5 +Stage,Hettangian,I1,#4EB3D3,199.5,201.4 +Stage,Rhaetian,t7,#E3B9DB,201.4,205.7 +Stage,Norian,t6,#D6AAD3,205.7,227.3 +Stage,Carnian,t5,#C99BCB,227.3,237.0 +Stage,Ladinian,t4,#C983BF,237.0,241.464 +Stage,Anisian,t3,#BC75B7,241.464,246.7 +Stage,Olenekian,t2,#B051A5,246.7,249.9 +Stage,Induan,t1,#A4469F,249.9,251.902 +Stage,Changhsingian,p9,#FCC0B2,251.902,254.14 +Stage,Wuchiapingian,p8,#FCB4A2,254.14,259.51 +Stage,Capitanian,p7,#FB9A85,259.51,264.28 +Stage,Wordian,p6,#FB8D76,264.28,266.9 +Stage,Roadian,p5,#FB8069,266.9,274.4 +Stage,Kungurian,p4,#E38776,274.4,283.3 +Stage,Artinskian,p3,#E37B68,283.3,290.1 +Stage,Sakmarian,p2,#E36F5C,290.1,293.52 +Stage,Asselian,p1,#E36350,293.52,298.9 +Stage,Gzhelian,c7,#CCD4C7,298.9,303.7 +Stage,Kasimovian,c6,#BFD0C5,303.7,307.0 +Stage,Moscovian,c5,#B3CBB9,307.0,315.2 +Stage,Bashkirian,c4,#99C2B5,315.2,323.4 +Stage,Serpukhovian,c3,#BFC26B,323.4,330.3 +Stage,Visean,c2,#A6B96C,330.3,346.7 +Stage,Tournaisian,c1,#8CB06C,346.7,358.86 +Stage,Famennian,d7,#F2EDB3,358.86,372.15 +Stage,Frasnian,d6,#F2EDAD,372.15,382.31 +Stage,Givetian,d5,#F1E185,382.31,387.95 +Stage,Eifelian,d4,#F1D576,387.95,393.47 +Stage,Emsian,d3,#E5D075,393.47,410.62 +Stage,Pragian,d2,#E5C468,410.62,413.02 +Stage,Lochkovian,d1,#E5B75A,413.02,419.62 +Stage,Pridoli,S4,#E6F5E1,419.62,422.7 +Stage,Ludfordian,s7,#D9F0DF,422.7,425.0 +Stage,Gorstian,s6,#CCECDD,425.0,426.7 +Stage,Homerian,s5,#CCEBD1,426.7,430.6 +Stage,Sheinwoodian,s4,#BFE6C3,430.6,432.9 +Stage,Telychian,s3,#BFE6CF,432.9,438.6 +Stage,Aeronian,s2,#B3E1C2,438.6,440.5 +Stage,Rhuddanian,s1,#A6DCB5,440.5,443.1 +Stage,Hirnantian,o7,#A6DBAB,443.1,445.2 +Stage,Katian,o6,#99D69F,445.2,452.8 +Stage,Sandbian,o5,#8CD094,452.8,458.2 +Stage,Darriwilian,o4,#74C69C,458.2,469.4 +Stage,Dapingian,o3,#66C092,469.4,471.3 +Stage,Floian,o2,#41B087,471.3,477.1 +Stage,Tremadocian,o1,#33A97E,477.1,486.85 +Stage,Stage 10,ep10,#E6F5C9,486.85,491.0 +Stage,Jiangshanian,ep9,#D9F0BB,491.0,494.2 +Stage,Paibian,ep8,#CCEBAE,494.2,497.0 +Stage,Guzhangian,ep7,#CCDFAA,497.0,500.5 +Stage,Drumian,ep6,#BFD99D,500.5,504.5 +Stage,Wuliuan,ep5,#B3D492,504.5,506.5 +Stage,Stage 4,ep4,#B3CA8E,506.5,514.5 +Stage,Stage 3,ep2,#A6C583,514.5,521.0 +Stage,Stage 2,ep3,#A6BA80,521.0,529.0 +Stage,Fortunian,ep1,#99B575,529.0,538.8 +Sub-Period,Pennsylvanian,C2,#7EBCC6,298.9,323.4 +Sub-Period,Mississippian,C1,#678F66,323.4,358.86 +Super-Eon,Precambrian,PE,#F74370,538.8,4567.0 diff --git a/pyleoclim/utils/datasets.py b/pyleoclim/utils/datasets.py index 29395811..a9066ff1 100644 --- a/pyleoclim/utils/datasets.py +++ b/pyleoclim/utils/datasets.py @@ -213,3 +213,196 @@ def load_dataset(name): raise RuntimeError(f"Unable to load dataset with file extension {metadata['file_extension']}.") return ts + + + + +def _first(it): + for x in it: + return x + return None + +def _localname(term): + s = str(term) + if "#" in s: + return s.rsplit("#", 1)[1] + return s.rstrip("/").rsplit("/", 1)[-1] + + + +def load_ics_chart_to_df(ttl_path_or_url="https://raw.githubusercontent.com/i-c-stratigraphy/chart/refs/heads/main/chart.ttl", time_units='Ma') -> pd.DataFrame: + + try: + from rdflib import Graph, Namespace, URIRef, Literal + from rdflib.namespace import SKOS, RDF + + print("Using rdflib to load GTS information from ICS") + TIME = Namespace("http://www.w3.org/2006/time#") + SCHEMA = Namespace("https://schema.org/") + GTS = Namespace("http://resource.geosciml.org/ontology/timescale/gts#") + + def _find_ns_for_local(graph, local): + """ + Find a predicate in the graph whose localname == `local` and + return its namespace base as an rdflib Namespace. Fallback to None. + """ + for s, p, o in graph.triples((None, None, None)): + if isinstance(p, URIRef) and _localname(p) == local: + base = str(p)[: -len(local)] + return Namespace(base) + return None + + g = Graph() + g.parse(ttl_path_or_url, format="turtle") + + # Resolve namespaces robustly + ischart_ns = _find_ns_for_local(g, "inMYA") # e.g., https://w3id.org/isc/isc2020# + if ischart_ns is None: + # Hard fallback (current ICS charts use this) + ischart_ns = Namespace("https://w3id.org/isc/isc2020#") + print("Warning: could not find 'inMYA' predicate in graph; using fallback namespace https://w3id.org/isc/isc2020#") + + # Rank mapping to your requested Rank labels + rank_map = { + "Eon": "Eon", "Eonothem": "Eon", + "Era": "Era", "Erathem": "Era", + "Period": "Period", "System": "Period", + "Epoch": "Epoch", "Series": "Epoch", + "Age": "Stage", "Stage": "Stage", + "Subepoch": "Subepoch", "Subseries": "Subepoch", + "Substage": "Substage", "Subperiod": "Subperiod", + } + + rows = [] + + # An interval concept typically has skos:prefLabel and gts:rank + for subj in set(g.subjects(SKOS.prefLabel, None)): + rank_uri = _first(g.objects(subj, GTS.rank)) + if rank_uri is None: + continue # skip non-interval resources + + # Type + rank_local = _localname(rank_uri) + Rank = rank_map.get(rank_local, rank_local) + + # Name + name_lit = _first(g.objects(subj, SKOS.prefLabel)) + Name = str(name_lit) if name_lit is not None else "" + + # Abbrev: skos:notation (typed literal is fine) + notation = _first(g.objects(subj, SKOS.notation)) + Abbrev = str(notation) if notation is not None else "" + + # Color (hex) if present + col = _first(g.objects(subj, SCHEMA.color)) + Color = str(col) if col is not None else "" + + # Helper to read a boundary node (blank node or URI) → inMYA float + def boundary_ma(node_pred): + node = _first(g.objects(subj, node_pred)) + if node is None: + return None + # find a predicate with localname 'inMYA' under this node + val = None + for p, o in g.predicate_objects(node): + if _localname(p) == "inMYA": + val = o + break + try: + return float(val) if val is not None else None + except Exception: + return None + + start_ma = boundary_ma(TIME.hasBeginning) + end_ma = boundary_ma(TIME.hasEnd) + + # UpperBoundary (younger/smaller Ma), LowerBoundary (older/larger Ma) + UpperBoundary = LowerBoundary = None + if start_ma is not None and end_ma is not None: + UpperBoundary = min(start_ma, end_ma) + LowerBoundary = max(start_ma, end_ma) + elif start_ma is not None: + UpperBoundary = start_ma + elif end_ma is not None: + UpperBoundary = end_ma + + # Keep intervals only + if not Name or (UpperBoundary is None and LowerBoundary is None): + continue + + rows.append({ + "Rank": Rank, + "Name": Name, + "Abbrev": Abbrev, + "Color": Color, + "UpperBoundary": UpperBoundary, + "LowerBoundary": LowerBoundary + }) + + df = pd.DataFrame(rows).dropna(subset=["Name", "Rank"]) + except ImportError as e: + print("Could not import rdflib; please install it to load GTS information from ICS. Loading from local CSV") + df = pd.read_csv(DATA_DIR.joinpath("i-c-stratigraphy_2024_11_24.csv"), skiprows=11 ) + df['UpperBoundary'] = pd.to_numeric(df['UpperBoundary'], errors='coerce') + df['LowerBoundary'] = pd.to_numeric(df['LowerBoundary'], errors='coerce') + df['Color'] = df['Color'].astype(str) + # Clean up + + + # Optional: order types, youngest first within each type + type_order = ["Eon", "Era", "Period", "Epoch", "Stage", "Subepoch", "Substage", "Subperiod"] + df["Rank"] = pd.Categorical(df["Rank"], categories=type_order + sorted(set(df["Rank"]) - set(type_order)), ordered=True) + df = df.sort_values(["Rank", "UpperBoundary"], na_position="last").reset_index(drop=True) + + if time_units not in ['Ma', 'Mya', 'My']: + if time_units in ['a', 'ya', 'y', 'yr']: + df['UpperBoundary'] = df['UpperBoundary'] * 1e6 + df['LowerBoundary'] = df['LowerBoundary'] * 1e6 + elif time_units in ['ka', 'kya', 'ky', 'kyr']: + df['UpperBoundary'] = df['UpperBoundary'] * 1e3 + df['LowerBoundary'] = df['LowerBoundary'] * 1e3 + print('converted to ka') + elif time_units in ['Ga', 'Gya', 'Gy', 'Gyr']: + df['UpperBoundary'] = df['UpperBoundary'] * 1e-3 + df['LowerBoundary'] = df['LowerBoundary'] * 1e-3 + else: + raise ValueError(f"Unsupported time_units '{time_units}'. Supported: 'Ma', 'ka', 'Ga'.") + + return df + +def apply_custom_traits(df, specs, target_col="Abbrev"): + """ + Apply custom overrides to a DataFrame by matching on traits. + + Parameters + ---------- + df : pandas.DataFrame + Must include the target column and all columns named in specs. + specs : list of dict + Each dict has at least the target_col key plus one or more + trait columns used for matching. + Example: {"Type":"Period", "Name":"Cambrian", "Abbrev":"Cam."} + target_col : str, default "Abbrev" + The column to update. + + Returns + ------- + pandas.DataFrame + Copy of df with updates applied. + """ + df = df.copy() + if not isinstance(specs, list): + specs = [specs] + for spec in specs: + if target_col not in spec: + raise ValueError(f"Spec {spec} missing '{target_col}' key") + + # Start with all True, then AND each condition + mask = pd.Series(True, index=df.index) + for col, val in spec.items(): + if col == target_col: + continue + mask &= df[col] == val + + df.loc[mask, target_col] = spec[target_col] + return df diff --git a/pyleoclim/utils/plotting.py b/pyleoclim/utils/plotting.py index bbf1ce4f..017b3315 100644 --- a/pyleoclim/utils/plotting.py +++ b/pyleoclim/utils/plotting.py @@ -4,7 +4,7 @@ Plotting utilities, leveraging Matplotlib. """ -__all__ = ['set_style', 'closefig', 'savefig'] +__all__ = ['set_style', 'closefig', 'savefig', 'add_GTS'] import matplotlib.pyplot as plt import pathlib @@ -13,10 +13,22 @@ import numpy as np import pandas as pd import collections.abc +import copy +from collections import defaultdict +from pathlib import Path +import re from ..utils import lipdutils +from ..utils import datasets + +import pandas as pd +import matplotlib.colors as mcolors +import matplotlib.patches as mpatches + +DATA_DIR = Path(__file__).parents[1].joinpath("data").resolve() + # import pandas as pd # from matplotlib.patches import Rectangle # from matplotlib.collections import PatchCollection @@ -822,13 +834,13 @@ def make_annotation_ax(fig, ax, loc='overlay', ax_d[ax_name] = make_phantom_ax(ax_d[ax_name]) ax_d[ax_name].set_facecolor((1, 1, 1, 0)) + return ax_d -import matplotlib.patches as mpatches -def hightlight_intervals(ax, intervals, labels=None, color='g', alpha=.3, legend=True): +def highlight_intervals(ax, intervals, labels=None, color='g', alpha=.3, legend=True): ''' Hightlights intervals This function highlights intervals. @@ -870,12 +882,12 @@ def hightlight_intervals(ax, intervals, labels=None, color='g', alpha=.3, legend ax=pyleo.utils.plotting.make_annotation_ax(fig, ax, ax_name = 'highlighted_intervals', zorder=-1) intervals = [[3, 8], [12, 18], [30, 31], [40,43], [49, 60], [60, 65]] - ax['highlighted_intervals'] = pyleo.utils.plotting.hightlight_intervals(ax['highlighted_intervals'], intervals, + ax['highlighted_intervals'] = pyleo.utils.plotting.highlight_intervals(ax['highlighted_intervals'], intervals, color='g', alpha=.1) ''' - if isinstance(intervals[0], list) is False: + if isinstance(intervals[0], (list, np.ndarray)) is False: intervals = [intervals] handles = [] @@ -884,25 +896,44 @@ def hightlight_intervals(ax, intervals, labels=None, color='g', alpha=.3, legend new_colors = [] new_alphas = [] + xlims = ax.get_xlim() for ik, _ts in enumerate(intervals): - if isinstance(color, list) is True: + if isinstance(color, (list, np.ndarray)) is True: c = color[ik] else: c = color + + if c in mpl.colors.CSS4_COLORS: + c = mpl.colors.CSS4_COLORS[c] + elif isinstance(c, str) and c.startswith('#'): + c = mpl.colors.to_rgba(c) + new_colors.append(c) - if isinstance(alpha, list) is True: + if isinstance(alpha, (list, np.ndarray)) is True: a = alpha[ik] else: a = alpha new_alphas.append(a) - if isinstance(labels, list) is True: + if isinstance(labels, (list, np.ndarray)) is True: label = labels[ik] else: label = '' new_labels.append(label) + if xlims[0] < xlims[1]: + if _ts[1] > xlims[1]: + _ts[1] = xlims[1] + if _ts[0] < xlims[0]: + _ts[0] = xlims[0] + else: + print(_ts[0], _ts[1], xlims[0], xlims[1]) + if _ts[1] < xlims[1]: + _ts[1] = xlims[1] + if _ts[0] > xlims[0]: + _ts[0] = xlims[0] + ax.axvspan(_ts[0], _ts[1], facecolor=c, alpha=a) return ax @@ -1242,7 +1273,7 @@ def make_scalar_mappable(cmap=None, hue_vect=None, n=None, norm_kwargs=None): return ax_sm -import copy + def consolidate_legends(ax, split_btwn=True, hue='relation', style='exp_type', size=None, colorbar=False): @@ -1437,3 +1468,684 @@ def keep_center_colormap(cmap, vmin, vmax, center=0): def tidy_labels(label): ''' Tidy up the label string''' return label.rstrip().lstrip().rstrip(',').lstrip(',') + + +def text_loc(fig, ax, rect, label_text, width, yloc): + ''' + Determine if the label fits inside or outside the bar. + + Parameters + ---------- + fig : matplotlib.figure.Figure + The figure object. + ax : matplotlib.axes.Axes + The axis where the bar is plotted. + rect : matplotlib.patches.Rectangle + The rectangle (bar) object. + label_text : str + The text label to check. + width : float + The width of the bar in data coordinates. + yloc : float + The y location of the bar in data coordinates. + + Returns + ------- + str + 'inside' if the label fits inside the bar, 'outside' otherwise. + + ''' + + + text = ax.text(yloc, rect.get_y() + rect.get_height() / 2, label_text, + va='center', ha='left', fontstretch='expanded') + + # Transform data width into display coords + renderer = fig.canvas.get_renderer() + bar_disp = ax.transData.transform((width, 0)) - ax.transData.transform((0, 0)) + bar_width_pixels = bar_disp[0] + bar_height_pixels = bar_disp[1] + + # Get label width in pixels + bbox = text.get_window_extent(renderer=renderer) + label_width_pixels = bbox.width + label_height_pixels = bbox.height + + if label_width_pixels < bar_width_pixels: + loc = 'inside' + else: + loc = 'outside' + text.remove() + return loc + +def add_GTS(fig, ax, GTS_df=None, ranks=None, time_units='Ma',location='above', label_pref='full', + allow_abbreviations=True, ax_name='gts', v_offset=0, height=.05, text_color=None, fontsize=None, edgecolor='k', + edgewidth=0, alpha=1,zorder=10,reverse_rank_order=True, + gts_url = "https://raw.githubusercontent.com/i-c-stratigraphy/chart/refs/heads/main/chart.ttl"): + + ''' + Adds the Geologic Time Scale (GTS) to the plot. + Parameters + ---------- + fig : matplotlib.figure.Figure + The figure object where the GTS will be added. + ax : dict + The axis where the GTS will be plotted. + GTS_df : pd.DataFrame, optional + DataFrame containing the GTS data with columns for 'Rank', 'Name', 'Abbrev', 'Color', 'UpperBoundary', 'LowerBoundary'. + If None, the function will load the ICS chart from the provided URL. + ranks : list of str, optional + List of ranks to include (e.g., ['Period', 'Epoch', 'Stage']). If None, defaults to ['Period', 'Epoch', 'Stage']. Ranks are ordered from outer-most to inner-most. + time_units : str, optional + Time units of the GTS data. Supported: 'Ma' (default), 'ka', 'Ga'. + location : str, optional + Specifies whether to place the GTS above or below the plot. Default is 'above'. + label_pref : str, optional + Preference for labels: 'full' (default) for full names, 'abbrev' for abbreviations, 'none' for no labels (only colors). + allow_abbreviations : bool, optional + If True, allows abbreviations for labels that don't fit. Default is True. + ax_name : str, optional + Name of the axis to create for the GTS. Default is 'gts'. + v_offset : float, optional + Vertical offset for the GTS labels. Default is 0. + height : float, optional + Height of the GTS annotation area. Default is 0.05. + text_color : str or None, optional + Color of the text labels. If None, the function will choose a contrasting color based on the bar color. Default is None. + fontsize : int, optional + Font size for the GTS labels. Default is 12. + edgecolor : str, optional + Color of the bar edges. Default is 'k' (black). + edgewidth : float, optional + Width of the bar edges. Default is 0. + alpha : float, optional + Transparency of the bars. Default is 1 (opaque). + zorder : int, optional + Z-order for the GTS axis. Default is 10. + gts_url : str, optional + URL to load the ICS chart if GTS_df is None. Default is the ICS chart URL. + + Returns + ------- + fig : matplotlib.figure.Figure + The figure object with the GTS added. + ax : dict + The axis dictionary with the GTS axis added. + + + Examples + -------- + + .. jupyter-execute:: + + import pyleoclim as pyleo + + ts_18 = pyleo.utils.load_dataset('cenogrid_d18O') + ts_13 = pyleo.utils.load_dataset('cenogrid_d13C') + ms = pyleo.MultipleSeries([ts_18, ts_13], label='Cenogrid', time_unit='ma BP') + + fig, ax = ms.stackplot(figsize=(10, 5),linewidth=0.5, fill_between_alpha=0) + ax[0].invert_yaxis() # d18O is traditionally inverted + + fig, ax = pyleo.utils.plotting.add_GTS(fig, ax, time_units='Ma', location='above', label_pref='full', + allow_abbreviations=True, ax_name='gts', v_offset=0, height=.05) + + ''' + + if isinstance(ax, dict) is False: + ax = {0: ax} + if fontsize is None: + pattern = re.compile(r".*(labelsize|fontsize).*") + fontsize_options = mpl.rcParams.find_all(pattern) + fontsize = min([value for value in fontsize_options.values() if isinstance(value, (int, float))]) + + if GTS_df is not None: + assert isinstance(GTS_df, pd.DataFrame) + assert all(col in GTS_df.columns for col in ['Rank', 'Name', 'Abbrev', 'Color', 'UpperBoundary', 'LowerBoundary']), "GTS_df must contain columns: ['Rank', 'Name', 'Abbrev', 'Color', 'UpperBoundary', 'LowerBoundary']" + assert GTS_df['UpperBoundary'].dtype in [np.float64, np.float32, np.int64, np.int32], "GTS_df 'UpperBoundary' must be numeric" + assert GTS_df['LowerBoundary'].dtype in [np.float64, np.float32, np.int64, np.int32], "GTS_df 'LowerBoundary' must be numeric" + duration = GTS_df['LowerBoundary'].values-GTS_df['UpperBoundary'].values + assert all(duration >= 0), "GTS_df 'LowerBoundary' must be greater than (further back in time) or equal to 'UpperBoundary (more modern)'" + + if GTS_df is None: + GTS_df = datasets.load_ics_chart_to_df(gts_url, time_units=time_units) + + if ranks is None: + ranks = ['Period', 'Epoch', 'Stage'] + + if 'x_axis' in ax.keys(): + xlims = ax['x_axis'].get_xlim() + else: + xlims = ax[0].get_xlim() + + # expects upper to be smaller than lower + need_to_swap = False + if xlims[0] > xlims[1]: + need_to_swap = True + + upper_lim = min(xlims) + lower_lim = max(xlims) + xlims = (upper_lim, lower_lim) + + # filter to only the range of interest + sub_df = GTS_df[GTS_df['Rank'].isin(ranks)] + sub_df = sub_df[(sub_df['UpperBoundary'] < lower_lim) & (sub_df['LowerBoundary'] > upper_lim)] + sub_df['UpperBoundary'] = sub_df['UpperBoundary'].apply(lambda x: max([x, upper_lim])) + sub_df['LowerBoundary'] = sub_df['LowerBoundary'].apply(lambda x: min([x, lower_lim])) + sub_df['duration'] = np.abs(sub_df['UpperBoundary'] - sub_df['LowerBoundary']) + sub_df.sort_values('LowerBoundary', ascending=False, inplace=True) + + num_ranks = len(ranks) + + # create the annotation axis + ax = make_annotation_ax(fig, ax, ax_name=ax_name, height=height * num_ranks, + loc=location, v_offset=v_offset, zorder=zorder) + ax[ax_name].spines[['top', 'bottom', 'left', 'right']].set_visible(False) + + # set limits and grid to make sure x-axis is increasing (will reverse at end if needed) + for ik, ax_key in enumerate(ax.keys()): + ax[ax_key].grid(visible=False) + ax[ax_key].set_xlim(xlims) + + k = 0 + text_color_flag = text_color # None means auto, otherwise use specified color + for unit in ranks: + unit_df = sub_df[sub_df['Rank']==unit].copy() + for i, row in unit_df.iterrows(): + color = row.Color + (r, g, b) = mcolors.to_rgb(color) + width = row.duration # width of the bar in data coords + y_loc = k * height # y location of the bar + rects = ax[ax_name].barh(y_loc, width=width, left=row.UpperBoundary, height=height, color=color, + edgecolor=edgecolor, linewidth=edgewidth, alpha=alpha) + + if label_pref == 'none': + continue + + # Render label to get its size in pixels + abbrev_loc = text_loc(fig, ax[ax_name], rects[0], row.Abbrev, width, y_loc) + full_label_loc = text_loc(fig, ax[ax_name], rects[0], row.Name, width, y_loc) + + + loc, label = 'outside', '' # default to outside with no label + if label_pref in ['abbrev', 'abbreviation']: + loc = abbrev_loc + label = row.Abbrev + else: + # full label preferred, but if it doesn't fit, try abbrev if allowed + if full_label_loc == 'inside': + loc = full_label_loc + label = row.Name + elif allow_abbreviations is True: + if abbrev_loc == 'inside': + loc = abbrev_loc + label = row.Abbrev + + if loc == 'inside': + if text_color_flag is None: + text_color = 'w' if r * g * b < 0.5 else 'darkgrey' + if b < .50: + if r * g > 0.85: + text_color = 'darkgrey' + elif r < .5: + if g * b > 0.85: + text_color = 'darkgrey' + + ax[ax_name].bar_label(rects, labels=[label], label_type='center', color=text_color, fontsize=fontsize) + + k += 1 + + # if reverse_rank_order is True: + if location in ['above', 'top']: + ax[ax_name].invert_yaxis() + + for ik, ax_key in enumerate(ax.keys()): + ax[ax_key].grid(False) + if need_to_swap is True: + ax[ax_key].invert_xaxis() + + + return fig, ax +# could test with [p.get_width() for p in ax['gts'].patches] + +# def check_text_fits_in_span(fig, ax, interval, label, fontsize=12, verbose=False): +# """ +# Checks if the given text can fit within the interval [x1, x2] when plotted. +# +# Parameters +# ---------- +# ax : matplotlib.axes.Axes +# The axis where the span is plotted. +# +# x1, x2 : float +# The interval in which the text should fit. +# +# text : str +# The text to check. +# +# fontsize : int, optional +# Font size to test the fitting, default is 12. +# +# Returns +# ------- +# bool +# True if text fits within [x1, x2], False otherwise. +# """ +# if verbose is True: +# print('fontsize', fontsize, 'label', label, 'interval', interval) +# fig.canvas.draw() # Ensure text positioning updates +# renderer = fig.canvas.get_renderer() +# +# # text_width1 = get_label_width(ax, label, buffer=0., fontsize=fontsize, verbose=verbose) +# +# # Create a test text object (invisible) +# test_text = ax.text(0, 0, label, ha='center', va='center', alpha=0, **{'fontsize': fontsize}) +# +# # Get text bounding box in display (pixel) coordinates +# bbox = test_text.get_window_extent(renderer=renderer) +# +# # Convert bbox from display units to data units +# bbox_data = ax.transData.inverted().transform(bbox) +# text_x_min, _ = bbox_data[0] +# text_x_max, _ = bbox_data[1] +# +# text_width = np.abs(text_x_max - text_x_min) # Text width in data coordinates +# test_text.remove() # Clean up test text +# +# # Compare text width with the available span width +# x1, x2 = interval +# span_width = np.abs(x2 - x1) +# if verbose is True: +# # print('get_label_width version', 'text width', text_width1, 'span width', span_width, +# # 'fits?', text_width1 <= span_width) +# print('native version', 'text width', text_width, 'span width', span_width, +# 'fits?', text_width <= span_width) +# +# return text_width <= span_width + + +# first attempt at managing outside labels +# def add_geol_labels(fig, _ax, ldf, key='Periods', y_gts=None, +# fontsize=10, allow_abbreviations=True, orientation='north', verbose=False): +# ax = _ax[key] +# xlims = ax.get_xlim() +# +# ax_outside_labels = None +# +# no_fits = defaultdict(list) +# for i in range(len(ldf)): +# ageStart = ldf.Start[i] +# ageEnd = ldf.End[i] +# ageMean = .5 * (ldf.Start[i] + ldf.End[i]) +# ageColor = ldf.Color[i] +# +# ageHandle = ldf.Name[i] +# +# ylims = ax.get_ylim() +# if y_gts is None: +# h = np.diff(ax.get_ylim())[0] +# y_gts = .5 * h # ylims[0] +# +# if xlims[0] < xlims[1]: +# left_age = min([ageStart, ageEnd]) +# right_age = max([ageStart, ageEnd]) +# +# if left_age < xlims[0]: +# left_age = xlims[0] +# fall_back_bound_ageMean = right_age - .5 * np.abs(right_age - left_age) +# time_value = max([ageMean, fall_back_bound_ageMean]) +# +# elif right_age > xlims[1]: +# right_age = xlims[1] +# fall_back_bound_ageMean = left_age + .5 * np.abs(right_age - left_age) +# time_value = min([ageMean, fall_back_bound_ageMean]) +# else: +# time_value = ageMean +# +# else: +# left_age = max([ageStart, ageEnd]) +# right_age = min([ageStart, ageEnd]) +# +# if left_age > xlims[0]: +# left_age = xlims[0] +# fall_back_bound_ageMean = right_age + .5 * np.abs(right_age - left_age) +# time_value = min([ageMean, fall_back_bound_ageMean]) +# elif right_age < xlims[1]: +# right_age = xlims[1] +# fall_back_bound_ageMean = left_age - .5 * np.abs(right_age - left_age) +# time_value = max([ageMean, fall_back_bound_ageMean]) +# else: +# time_value = ageMean +# +# if isinstance(ageHandle, str) == False: +# ageHandle = '' +# text_obj = ax.text(time_value, y_gts, ageHandle, +# ha='center', +# va='center', +# fontsize=fontsize, +# rotation=0) +# if verbose is True: +# print(ageHandle, time_value, y_gts, 'left', left_age, 'right', right_age) +# fit_bool = check_text_fits_in_span(fig, ax, [left_age, right_age], ageHandle, +# verbose=verbose) # check_fits(ax, rect, text_obj) +# if fit_bool == False: +# text_obj.remove() +# +# if allow_abbreviations == False: +# no_fits['handle'].append(ageHandle) +# no_fits['time_value'].append(time_value) +# if ax_outside_labels is None: +# # _ax=pyleo.utils.plotting.make_annotation_ax(fig, _ax, ax_name = 'outside_labels', height=.06, +# # loc='above', v_offset=.02,zorder=-2) +# ax_outside_labels = True +# # _ax['outside_labels'].spines['top'].set_visible(False) +# +# else: +# ageHandle = ldf.Abbrev[i] +# fit_bool = check_text_fits_in_span(fig, ax, [left_age, right_age], ageHandle, +# verbose=verbose) # check_fits(ax, rect, text_obj) +# if fit_bool == True: +# if isinstance(ageHandle, str) == False: +# ageHandle = '' +# text_obj = ax.text(time_value, y_gts, ageHandle, +# # fontname='TeX Gyre Heros', +# ha='center', +# va='center', +# fontsize=fontsize, +# rotation=0) +# else: +# try: +# text_obj.remove() +# except: +# if verbose is True: +# print('text_obj already removed') +# +# if ax_outside_labels is not None: +# if verbose is True: +# print('adding outside labels', _ax['outside_labels'].get_ylim()) +# if orientation == 'south': +# valign = 'top' +# else: +# valign = 'bottom' +# orientation = 'north' +# _ax['outside_labels'] = label_intervals( +# fig, _ax['outside_labels'], +# no_fits['handle'], no_fits['time_value'], +# orientation=orientation, baseline=1, height=0.35, buffer=0.12, +# linestyle_kwargs={'color': 'gray'}, text_kwargs={'fontsize': fontsize, 'va': valign}) +# # +# + +# def set_placement(loc, epochs, periods, stages): +# label_d = {'epochs': epochs, 'periods': periods, 'stages': stages} +# ik = 0 +# for key in ['stages', 'epochs', 'periods']: +# if label_d[key] is True: +# label_d[key] = ik +# if loc == 'above': +# ik += 1 +# if loc == 'below': +# ik -= 1 +# +# stages = label_d['stages'] +# epochs = label_d['epochs'] +# periods = label_d['periods'] +# return epochs, periods, stages +# + +# def get_v_offsets(loc, height, num_offsets, v_offset_0=None): +# v_offset_d = {} +# if loc == 'above': +# v_offset_0 = v_offset_0 if v_offset_0 is not None else .01 +# v_offset_d['v_offset_0'] = v_offset_0 +# for ik in range(num_offsets): +# v_offset_d[ik] = v_offset_0 # + ik*height +# +# else: +# v_offset_0 = v_offset_0 if v_offset_0 is not None else -.01 +# v_offset_d['v_offset_0'] = v_offset_0 +# for ik in range(num_offsets): +# v_offset_d[-ik] = v_offset_0 # - ik*height +# +# return v_offset_d # {'v_offset_0': v_offset_0, 'inner':v_offset_inner, 'outer':v_offset_outer} + + +# def summarize_geol(grp): +# """ +# Function to summarize the geologic time scale data. +# """ +# group_name = [col for col in ['Period', 'Epoch', 'Stage'] if col in grp.columns][0] +# color_var = group_name + '_Color' +# abbrev_var = group_name + '_Abbrev' +# return pd.Series({ +# 'Name': grp[group_name].iloc[0], +# 'End': grp['UpperBoundary'].min(), +# 'Start': grp['LowerBoundary'].max(), +# 'Abbrev': grp[abbrev_var].iloc[0], +# 'Color': grp[color_var].iloc[0] +# }) + + +# def add_GTS(fig, ax, epochs, periods=False, stages=False, allow_abbreviations=True, location='below', height=.045, +# v_offset=None, fontsize=10, verbose=False, zorder=-2): +# """ +# Add the Geologic Time Scale (GTS) to a matplotlib figure. +# Parameters +# ---------- +# fig : matplotlib.figure.Figure +# The figure to which the GTS will be added. +# ax : matplotlib.axes.Axes +# The axes to which the GTS will be added. +# epochs : bool +# If True, add epochs to the GTS. +# periods : bool +# If True, add periods to the GTS. +# stages : bool +# If True, add stages to the GTS. +# allow_abbreviations : bool +# If True, allow abbreviations for the GTS labels. +# location : str +# The location of the GTS labels, either 'above' or 'below'. +# height : float +# The height of the GTS labels. +# v_offset : float, optional +# The vertical offset for the GTS labels. If None, a default value will be used. +# fontsize : int +# The font size for the GTS labels. +# verbose : bool +# If True, print additional information for debugging. +# zorder : int +# The z-order for the GTS labels, determining their drawing order relative to other elements. +# Returns +# ------- +# fig : matplotlib.figure.Figure +# The figure with the GTS added. +# ax : matplotlib.axes.Axes +# The axes with the GTS added. +# Notes +# ----- +# This function reads the Geologic Time Scale data from a CSV file named 'GTS_updated.csv'. +# The CSV file should contain columns for 'Stage', 'UpperBoundary', 'LowerBoundary', 'Period_Abbrev', +# 'Epoch_Abbrev', 'Stage_Abbrev', 'Period_Color', 'Epoch_Color', and 'Stage_Color'. +# The function highlights intervals for stages, epochs, and periods based on the boundaries defined in the CSV file. +# The function also adds labels for stages, epochs, and periods to the specified axes. +# If the `allow_abbreviations` parameter is set to False, the full names of stages, epochs, and periods will be used. +# +# Examples +# -------- +# .. jupyter-execute:: +# +# import pyleoclim as pyleo +# import matplotlib.pyplot as plt +# +# ts_18 = pyleo.utils.load_dataset('cenogrid_d18O') +# ts_13 = pyleo.utils.load_dataset('cenogrid_d13C') +# ms = pyleo.MultipleSeries([ts_18, ts_13], label='Cenogrid', time_unit='ma BP') +# +# fig, ax = ms.stackplot(figsize=(8, 5),linewidth=0.5, fill_between_alpha=0) +# +# for ik, ax_name in enumerate(ax.keys()): +# ax[ax_name].invert_xaxis() +# ax[0].invert_yaxis() +# +# fig, ax = pyleo.utils.plotting.add_GTS(fig, ax, epochs=True, periods=True, stages=True, +# allow_abbreviations=False, location='below', height=0.04, +# v_offset=0.01, fontsize=10) +# """ +# +# GT_csv = 'GTS_updated.csv' +# gt_path = DATA_DIR.joinpath(f"{GT_csv}") +# GTS_df = pd.read_csv(gt_path) +# GTS_df[['UpperBoundary', 'LowerBoundary']] = GTS_df[['UpperBoundary', 'LowerBoundary']].apply(pd.to_numeric, +# errors='coerce') +# +# num_offsets = epochs + periods + stages +# offsets = get_v_offsets(location, height, num_offsets, v_offset) +# height_num = epochs + periods + stages + 1 +# if allow_abbreviations is False: +# height = height_num * height + offsets['v_offset_0'] +# ax = make_annotation_ax(fig, ax, ax_name='outside_labels', height=height, +# loc=location, v_offset=offsets['v_offset_0'], zorder=zorder) +# ax['outside_labels'].spines[['top', 'bottom', 'left', 'right']].set_visible(False) +# +# xlims = ax['x_axis'].get_xlim() +# print(xlims) +# epochs_loc, periods_loc, stages_loc = set_placement(location, epochs, periods, stages) +# for loc in [stages_loc, epochs_loc, periods_loc]: +# if loc is False: +# continue +# if loc == stages_loc: +# stage_ax_name = -stages_loc + 1000 # 'Stages' +# ax =make_annotation_ax(fig, ax, ax_name=stage_ax_name, height=height, +# loc=location, v_offset=offsets[stages_loc], zorder=zorder) +# ax[stage_ax_name].spines[['top', 'bottom', 'left', 'right']].set_visible(False) +# ax[stage_ax_name].set_xlim(xlims) +# ax[stage_ax_name].grid(visible=False) +# +# ldf = GTS_df[['Stage', 'UpperBoundary', 'LowerBoundary', 'Period_Abbrev', 'Epoch_Abbrev', 'Stage_Abbrev', +# 'Period_Color', 'Epoch_Color', 'Stage_Color']].groupby('Stage').apply( +# summarize_geol).sort_values('End').reset_index() +# ldf = ldf.loc[ldf.End <= max(xlims)] +# intervals = ldf[['End', 'Start']].values.reshape(-1, 2) +# ax[stage_ax_name] = hightlight_intervals(ax[stage_ax_name], intervals, color=ldf['Color'].values, alpha=1) +# add_geol_labels(fig, ax, ldf, key=stage_ax_name, y_gts=.5, fontsize=fontsize, +# allow_abbreviations=allow_abbreviations, verbose=verbose) +# ax[stage_ax_name].grid(False) +# +# elif loc == epochs_loc: +# epoch_ax_name = -epochs_loc + 1000 # 'Epochs' +# ax = make_annotation_ax(fig, ax, ax_name=epoch_ax_name, height=height, +# loc=location, v_offset=offsets[epochs_loc], +# zorder=zorder) +# ax[epoch_ax_name].spines[['top', 'bottom', 'left', 'right']].set_visible(False) +# ax[epoch_ax_name].set_xlim(xlims) +# +# ldf = GTS_df[['Epoch', 'UpperBoundary', 'LowerBoundary', 'Period_Abbrev', 'Epoch_Abbrev', 'Stage_Abbrev', +# 'Period_Color', 'Epoch_Color', 'Stage_Color']].groupby('Epoch').apply( +# summarize_geol).sort_values('End').reset_index() +# ldf = ldf.loc[ldf.End <= max(xlims)] +# intervals = ldf[['End', 'Start']].values.reshape(-1, 2) +# ax[epoch_ax_name] = hightlight_intervals(ax[epoch_ax_name], intervals, color=ldf['Color'].values, alpha=1) +# add_geol_labels(fig, ax, ldf, key=epoch_ax_name, y_gts=.45, fontsize=fontsize, +# allow_abbreviations=allow_abbreviations, orientation='north', verbose=verbose) +# ax[epoch_ax_name].grid(False) +# +# elif loc == periods_loc: +# period_ax_name = -periods_loc + 1000 # 'Periods' +# ax = make_annotation_ax(fig, ax, ax_name=period_ax_name, height=height, +# loc=location, v_offset=offsets[periods_loc], +# zorder=zorder) +# ax[period_ax_name].spines[['top', 'bottom', 'left', 'right']].set_visible(False) +# ax[period_ax_name].set_xlim(xlims) +# ax[period_ax_name].grid(visible=False) +# +# ldf = GTS_df[['Period', 'UpperBoundary', 'LowerBoundary', 'Period_Abbrev', 'Epoch_Abbrev', 'Stage_Abbrev', +# 'Period_Color', 'Epoch_Color', 'Stage_Color']].groupby('Period').apply( +# summarize_geol).sort_values('End').reset_index() +# ldf = ldf.loc[ldf.End <= max(xlims)] +# intervals = ldf[['End', 'Start']].values.reshape(-1, 2) +# ax[period_ax_name] = hightlight_intervals(ax[period_ax_name], intervals, color=ldf['Color'].values, alpha=1) +# add_geol_labels(fig, ax, ldf, key=period_ax_name, y_gts=.5, fontsize=fontsize, +# allow_abbreviations=allow_abbreviations, verbose=verbose) +# ax[period_ax_name].grid(False) +# +# return fig, ax + +# def get_label_width(ax, label, buffer=0., fontsize=10., verbose=False): +# renderer = ax.figure.canvas.get_renderer() +# text = ax.text(0, 0, label, **dict(fontsize=float(fontsize))) +# bbox = text.get_window_extent(renderer=renderer) +# text.remove() +# +# # Convert pixel bbox to data coordinates +# data_bbox = ax.transData.inverted().transform(bbox) +# width = np.abs(data_bbox[1][0] - data_bbox[0][0]) +# +# if verbose is True: +# print(f"Label: {label}, Width: {width}, Buffer: {buffer}") +# return width + buffer + +# def calculate_overlapping_sets(fig, ax, labels, x_locs, fontsize, buffer=0.1, verbose=False): +# # calls `check_text_fits_in_span` +# +# overlapping_sets = [] +# current_set = {0} # Start with the first label +# n_labels = len(labels) +# xlims = ax.get_xlim() +# x_loc = [xlims[0]] +# x_loc += x_locs +# # x_locs = [].extend(x_locs) +# x_loc.append(xlims[1]) +# x_locs = x_loc +# +# # check if left fits in its slot. if left fits, but middle +# for i in range(2, n_labels): +# label_in_question = labels[i - 1] +# interval = (x_locs[i - 1], x_locs[i + 1]) +# # Check if label fits in the interval between its neighbor stems +# fits = check_text_fits_in_span(fig, ax, interval, label_in_question, fontsize=fontsize) +# if verbose is True: +# print('center', label_in_question, fits, interval) +# +# # left +# #if time 0 is on right +# label_in_question_r = labels[i - 2] +# interval_r = (x_locs[i - 2], x_locs[i - 1]) +# fits_right = check_text_fits_in_span(fig, ax, interval_r, label_in_question_r, fontsize=fontsize) +# interval_l = (x_locs[i - 1], x_locs[i]) +# fits_left = check_text_fits_in_span(fig, ax, interval_l, label_in_question_r, fontsize=fontsize) +# if verbose is True: +# print('before label', label_in_question_r, '(left)', fits_left, interval_l, +# '(right)', fits_right, interval_r) +# +# label_in_question_l = labels[i] +# interval_l = (x_locs[i], x_locs[i + 1]) +# fits_left = check_text_fits_in_span(fig, ax, interval_l, label_in_question_l, fontsize=fontsize) +# interval_r = (x_locs[i - 1], x_locs[i]) +# fits_right = check_text_fits_in_span(fig, ax, interval_r, label_in_question_l, fontsize=fontsize) +# if verbose is True: +# print('after label', label_in_question_l, '(left)', fits_left, interval_l, +# '(right)', fits_right, interval_r) +# +# interval = (x_locs[i], x_locs[i + 1]) +# fits_right = check_text_fits_in_span(fig, ax, interval, labels[i], fontsize=fontsize) +# # print('fits right', labels[i], fits_right, interval) +# +# interval = (x_locs[i - 1], x_locs[i]) +# fits_left = check_text_fits_in_span(fig, ax, interval, labels[i - 1], fontsize=fontsize) +# if verbose is True: +# print(label_in_question, 'fits', fits, 'fits left', fits_left, 'fits right', fits_right) +# +# # If neither label fits within their interval, they overlap +# if not fits_left or not fits_right: +# current_set.add(i) +# else: +# overlapping_sets.append(sorted(list(current_set))) +# current_set = {i} +# +# # Append last set +# overlapping_sets.append(sorted(list(current_set))) +# if verbose is True: +# print('overlapping sets', overlapping_sets) +# return overlapping_sets +