From 4938bcdedc4191b215656c4f5ee9ca260c5cee79 Mon Sep 17 00:00:00 2001 From: Yan Wong Date: Thu, 25 Jul 2019 19:22:41 -0700 Subject: [PATCH] Start addressing https://github.com/OneZoom/OZtree/issues/183 --- .../OZTreeModule/src/factory/data_repo.js | 3 +- .../rawJS/OZTreeModule/src/factory/midnode.js | 38 ++++++++---- .../rawJS/OZTreeModule/src/factory/utils.js | 23 +++++-- .../src/projection/layout/AT/node_layout.js | 10 +-- .../projection/layout/leaf_layout_helper.js | 28 ++++++--- .../projection/layout/node_layout_helper.js | 20 +++--- .../OZTreeModule/src/themes/natural_theme.js | 62 ++++++++++++------- views/treeviewer/js_strings.json | 13 +++- 8 files changed, 130 insertions(+), 67 deletions(-) diff --git a/OZprivate/rawJS/OZTreeModule/src/factory/data_repo.js b/OZprivate/rawJS/OZTreeModule/src/factory/data_repo.js index 10b34a55..032bfa79 100755 --- a/OZprivate/rawJS/OZTreeModule/src/factory/data_repo.js +++ b/OZprivate/rawJS/OZTreeModule/src/factory/data_repo.js @@ -257,6 +257,7 @@ function parse_ordered_leaves(data_repo, leaves, node_details) { leaf_meta_entry[data_repo.mc_key_l["OTTid"]] = ott; leaf_meta_entry[data_repo.mc_key_l["scientificName"]] =leaves[i][node_details.leaf_cols["name"]]; leaf_meta_entry[data_repo.mc_key_l["popularity"]] = leaves[i][node_details.leaf_cols["popularity"]]; + leaf_meta_entry[data_repo.mc_key_l["extinction_date"]] = leaves[i][node_details.leaf_cols["extinction_date"]]; } } @@ -269,7 +270,7 @@ function parse_ordered_nodes(data_repo, nodes, node_details) { node_meta_entry[data_repo.mc_key_n["OTTid"]] = ott; node_meta_entry[data_repo.mc_key_n["scientificName"]] = nodes[i][node_details.node_cols["name"]]; node_meta_entry[data_repo.mc_key_n["popularity"]] = nodes[i][node_details.node_cols["popularity"]]; - node_meta_entry[data_repo.mc_key_n["lengthbr"]] = Math.abs(nodes[i][node_details.node_cols["age"]]); + node_meta_entry[data_repo.mc_key_n["age_Ma"]] = Math.abs(nodes[i][node_details.node_cols["age"]]); node_meta_entry[data_repo.mc_key_n["sp1"]] = nodes[i][node_details.node_cols["{pic}1"]]; node_meta_entry[data_repo.mc_key_n["sp2"]] = nodes[i][node_details.node_cols["{pic}2"]]; diff --git a/OZprivate/rawJS/OZTreeModule/src/factory/midnode.js b/OZprivate/rawJS/OZTreeModule/src/factory/midnode.js index 2d099db6..8ae5d790 100755 --- a/OZprivate/rawJS/OZTreeModule/src/factory/midnode.js +++ b/OZprivate/rawJS/OZTreeModule/src/factory/midnode.js @@ -25,13 +25,13 @@ class Midnode { this._sponsor_name = null; this._sponsor_kind = null; this._sponsor_extra = null; - this._age = null; + this._age = undefined; this._spec_num_full = null; this._picset_len = null; this._picset_codes = null; this._signpost_common = false; this._threatened_branch = null; - this._redlist = null; + this._redlist = undefined; this._pic_filename = null; this._picID_credit = null; this._picID_src = null; @@ -90,13 +90,13 @@ class Midnode { this._sponsor_name = null; this._sponsor_kind = null; this._sponsor_extra = null; - this._age = null; + this._age = undefined; this._spec_num_full = null; this._picset_len = null; this._picset_codes = null; this._signpost_common = false; this._threatened_branch = null; - this._redlist = null; + this._redlist = undefined; this._pic_filename = null; this._picID_credit = null; this._picID_src = null; @@ -239,7 +239,7 @@ class Midnode { /** - * Get attribute of node by key name. Use this function to fetch metadata of node only. + * Get attribute of node or leaf by key name from the data_repo store. */ get_attribute(key_name) { if (this.detail_fetched && this.is_leaf) { @@ -327,14 +327,28 @@ class Midnode { } return _sponsor_extra; } - get lengthbr() { - if (this._age !== null) return this._age; - let age = this.get_attribute("lengthbr"); - age = isNaN(age) ? 0 : age; + get age_Ma() { + /* used for internal node dates - if 0 or null, this is unknown */ + if (this._age !== undefined) return this._age; + let _age_Ma = this.get_attribute("age_Ma"); + _age_Ma = isNaN(_age_Ma) ? 0 : _age_Ma; if (this.detail_fetched) { - this._age = age; + this._age = _age_Ma; } - return age; + return _age_Ma; + } + get extinction_Ma() { + /* used for leaf dates. Extant are null, unknown is marked by a large negative value + (-1e4: more Mya than the age of the planet). In this case we simply return `true`. + Zero might mark something very recently extinct, e.g. marked as extinct by IUCN + */ + if (this._age !== undefined) return this._age; + let _extinction_Ma = this.get_attribute("extinction_date"); + _extinction_Ma = (_extinction_Ma < -5e3)?true:_extinction_Ma + if (this.detail_fetched) { + this._age = _extinction_Ma; + } + return _extinction_Ma; } get spec_num_full() { if (this._spec_num_full !== null) return this._spec_num_full; @@ -396,7 +410,7 @@ class Midnode { return num_threatened > this.richness_val * 0.5; } get redlist() { - if (this._redlist !== null) return this._redlist; + if (this._redlist !== undefined) return this._redlist; let _redlist = this.get_attribute("IUCN"); if (this.detail_fetched) { this._redlist = _redlist; diff --git a/OZprivate/rawJS/OZTreeModule/src/factory/utils.js b/OZprivate/rawJS/OZTreeModule/src/factory/utils.js index 331f1881..5499ba0c 100755 --- a/OZprivate/rawJS/OZTreeModule/src/factory/utils.js +++ b/OZprivate/rawJS/OZTreeModule/src/factory/utils.js @@ -58,15 +58,26 @@ export function gpmapper(datein, full) { } -export function ageAsText(Ma) { - //return e.g. 100 thousand years ago +export function ageAsText(Ma, leaf) { + //return e.g. "100 thousand years ago", or a similar string + let subs_strs = leaf?OZstrings['leaf_date']:OZstrings['node_date']; if (Ma >10) { - return OZstrings['Mya'].replace(/\{(\w+)\}/g, function (m, c) {return({'mya':(Math.round(Ma*10)/10.0).toString()}[c])}); + return subs_strs['Mya'].replace( + /\{(\w+)\}/g, + function (m, c) {return({'mya':(Math.round(Ma*10)/10.0).toString()}[c])}); } else { - if (Ma >1) { - return OZstrings['Mya'].replace(/\{(\w+)\}/g, function (m, c) {return({'mya':(Math.round(Ma*100)/100.0).toString()}[c])}); + if (Ma > 1) { + return subs_strs['Mya'].replace( + /\{(\w+)\}/g, + function (m, c) {return({'mya':(Math.round(Ma*100)/100.0).toString()}[c])}); + } else if (Ma > 0.001) { + return subs_strs['tya'].replace( + /\{(\w+)\}/g, + function (m, c) {return({'tya':(Math.round(Ma*10000)/10.0).toString()}[c])}); } else { - return OZstrings['tya'].replace(/\{(\w+)\}/g, function (m, c) {return({'tya':(Math.round(Ma*10000)/10.0).toString()}[c])}); + return subs_strs['ya'].replace( + /\{(\w+)\}/g, + function (m, c) {return({'ya':(Math.round(Ma*1000000)).toString()}[c])}); } } } diff --git a/OZprivate/rawJS/OZTreeModule/src/projection/layout/AT/node_layout.js b/OZprivate/rawJS/OZTreeModule/src/projection/layout/AT/node_layout.js index a3620de9..215e49a0 100755 --- a/OZprivate/rawJS/OZTreeModule/src/projection/layout/AT/node_layout.js +++ b/OZprivate/rawJS/OZTreeModule/src/projection/layout/AT/node_layout.js @@ -58,16 +58,16 @@ class ATNodeLayout extends NodeLayoutBase { function get_concestor_interior_header(node) { let textonly_header; let concestor_append = "Concestor " + node.concestor + ","; - if (node.lengthbr && node.lengthbr>0) { + if (node.age_Ma && node.age_Ma>0) { //This is a dated node if (is_primary_or_secondary_name(node)) { textonly_header = (["", "", "the most recent common ancestor to today’s", - "the " + gpmapper(node.lengthbr) + " period, lived " + concestor_append, - ageAsText(node.lengthbr) + ", during"]); + "the " + gpmapper(node.age_Ma) + " period, lived " + concestor_append, + ageAsText(node.age_Ma) + ", during"]); } else { textonly_header = (["", "common ancestor to species including", "lived " + concestor_append + " the most recent", - "during the " + gpmapper(node.lengthbr) + " period,", - ageAsText(node.lengthbr)]) + "during the " + gpmapper(node.age_Ma) + " period,", + ageAsText(node.age_Ma)]) } } else { //This is an undated node (shouldn't happen) diff --git a/OZprivate/rawJS/OZTreeModule/src/projection/layout/leaf_layout_helper.js b/OZprivate/rawJS/OZTreeModule/src/projection/layout/leaf_layout_helper.js index 6fbbb6a4..90068a00 100755 --- a/OZprivate/rawJS/OZTreeModule/src/projection/layout/leaf_layout_helper.js +++ b/OZprivate/rawJS/OZTreeModule/src/projection/layout/leaf_layout_helper.js @@ -13,7 +13,7 @@ import {global_button_action} from '../../button_manager'; import {live_area_config} from '../live_area_config'; import {add_mr} from '../move_restriction'; import {get_image, image_ready} from '../../image_cache'; -import {extxt, spec_num_full} from '../../factory/utils'; +import {extxt, spec_num_full, ageAsText} from '../../factory/utils'; import config from '../../global_config'; class LeafLayoutBase { @@ -156,16 +156,24 @@ class LeafLayoutBase { } get_conservation_text(node) { - if (node.redlist === "EX" - ||node.redlist === "EW" - ||node.redlist === "CR" - ||node.redlist === "EN" - ||node.redlist === "VU" - ||node.redlist === "NT" - ||node.redlist === "LC") { - return [OZstrings["Conservation"], OZstrings["IUCN Red List status:"], extxt(node)] + if (node.extinction_Ma !== null) { + if (node.extinction_Ma !== true) { + return ageAsText(node.extinction_Ma, true).split("\n"); + } else { + return OZstrings["Fossil species"].split("\n"); + }; } else { - return []; + if (node.redlist === "EX" + ||node.redlist === "EW" + ||node.redlist === "CR" + ||node.redlist === "EN" + ||node.redlist === "VU" + ||node.redlist === "NT" + ||node.redlist === "LC") { + return [OZstrings["Conservation"], OZstrings["IUCN Red List status:"], extxt(node)] + } else { + return []; + } } } diff --git a/OZprivate/rawJS/OZTreeModule/src/projection/layout/node_layout_helper.js b/OZprivate/rawJS/OZTreeModule/src/projection/layout/node_layout_helper.js index 0eb62668..24bb0bf5 100755 --- a/OZprivate/rawJS/OZTreeModule/src/projection/layout/node_layout_helper.js +++ b/OZprivate/rawJS/OZTreeModule/src/projection/layout/node_layout_helper.js @@ -601,12 +601,12 @@ class NodeLayoutBase { get_date_str(node) { let date_str; - if (node.lengthbr > 10) { - date_str = (Math.round((node.lengthbr)*10)/10.0).toString() + " Ma"; - } else if (node.lengthbr > 1) { - date_str = (Math.round((node.lengthbr)*100)/100.0).toString() + " Ma"; - } else if (node.lengthbr > 0) { - date_str = (Math.round((node.lengthbr)*10000)/10.0).toString() + " Ka"; + if (node.age_Ma > 10) { + date_str = (Math.round((node.age_Ma)*10)/10.0).toString() + " Ma"; + } else if (node.age_Ma > 1) { + date_str = (Math.round((node.age_Ma)*100)/100.0).toString() + " Ma"; + } else if (node.age_Ma > 0) { + date_str = (Math.round((node.age_Ma)*10000)/10.0).toString() + " Ka"; } else { date_str = ""; } @@ -616,8 +616,8 @@ class NodeLayoutBase { get_textonly_header(node) { let ntxt = OZstrings['node_labels']['text_only'] let textonly_header; - if (node.lengthbr && node.lengthbr>0) { - let vars = {'date_with_units':ageAsText(node.lengthbr), 'geo_time':gpmapper(node.lengthbr, true)}; + if (node.age_Ma && node.age_Ma>0) { + let vars = {'date_with_units':ageAsText(node.age_Ma), 'geo_time':gpmapper(node.age_Ma, true)}; if (is_primary_or_secondary_name(node)) { textonly_header = substitute_variables(ntxt['dated']['named'], vars).split("\n").reverse(); } else { @@ -637,8 +637,8 @@ class NodeLayoutBase { let ntxt = OZstrings['node_labels']['with_pic'] let pic_header_text; //NB - these are in reverse order, as we often don't use the top two lines - if (node.lengthbr && (node.lengthbr>0)) { - let vars = {'date_with_units':ageAsText(node.lengthbr), 'geo_time':gpmapper(node.lengthbr, true)}; + if (node.age_Ma && (node.age_Ma>0)) { + let vars = {'date_with_units':ageAsText(node.age_Ma), 'geo_time':gpmapper(node.age_Ma, true)}; if (is_primary_or_secondary_name(node)) { pic_header_text = substitute_variables(ntxt['dated']['named'], vars).split("\n").reverse(); } else { diff --git a/OZprivate/rawJS/OZTreeModule/src/themes/natural_theme.js b/OZprivate/rawJS/OZTreeModule/src/themes/natural_theme.js index 33efc798..17552887 100755 --- a/OZprivate/rawJS/OZTreeModule/src/themes/natural_theme.js +++ b/OZprivate/rawJS/OZTreeModule/src/themes/natural_theme.js @@ -22,6 +22,13 @@ let iucnDD = green1; let iucnNE = green1; let iucnDefault = green1; +function is_fossil(leaf) { + // fossils have extinction_Ma > 0 (if known) or true + // extant (or recently extinct) leaves should all have extinction_Ma===null + // we use == not === to also treat undefined (data not yet filled out) as not fossil + return (leaf.extinction_Ma == null)?false:true; +} + function outline_highlight(node) { if (node.richness_val > 1) { return 'rgb(0,0,0)'; @@ -33,25 +40,29 @@ function outline_highlight(node) { function leafcolor2b(node) { switch(node.redlist) { case "EX": - return ('rgb(50,50,50)'); + return ('rgb(50,50,50)'); case "EW": - return ('rgb(50,50,50)'); + return ('rgb(50,50,50)'); case "CR": - return ('rgb(80,00,00)'); + return ('rgb(80,00,00)'); case "EN": - return ('rgb(80,00,00)'); + return ('rgb(80,00,00)'); case "VU": - return ('rgb(80,00,00)'); + return ('rgb(80,00,00)'); case "NT": - return ('rgb(20,80,00)'); + return ('rgb(20,80,00)'); case "LC": - return ('rgb(20,80,00)'); + return ('rgb(20,80,00)'); case "DD": - return ('rgb(20,80,00)'); + return ('rgb(20,80,00)'); case "NE": - return ('rgb(20,80,00)'); + return ('rgb(20,80,00)'); default: - return ('rgb(20,80,00)'); + if (is_fossil(node)) { + return ('rgb(50,50,50)'); + } else { + return ('rgb(20,80,00)'); + } } } @@ -69,42 +80,47 @@ function get_redlist_color(node) { switch(node.redlist) { case "EX": //return ('rgb(150,175,215)'); // new blue - return iucnEX; + return iucnEX; case "EW": //return ('rgb(150,175,215)'); // new blue - return iucnEW; + return iucnEW; //return ('rgb(80,80,80)'); case "CR": - return iucnCR; + return iucnCR; //////////return (red1); //'rgb(215,175,150)' = dinah pink case "EN": //return ('rgb(225,185,130)'); //return ('rgb(210,170,145)'); - return iucnEN; + return iucnEN; //////////return (red1); case "VU": - return iucnVU; + return iucnVU; //return (red1); //return ('rgb(210,170,145)'); //return ('rgb(220,220,220)'); case "NT": - return iucnNT; + return iucnNT; //return ('rgb(170,195,96)'); //return ('rgb(180,208,90)'); //return ('rgb(200,220,180)'); //return ('rgb(190,200,80)'); case "LC": - return iucnLC; + return iucnLC; case "DD": - return iucnDD; + return iucnDD; //return ('rgb(60,50,135)'); case "NE": - return iucnNE; + return iucnNE; //return ('rgb(0,0,190)'); default: - return iucnDefault; + if (is_fossil(node)) { + return iucnEX; + } else { + // extant leaves should all have age==null or age==0 + return iucnDefault; + } } } @@ -146,7 +162,11 @@ function get_redlist_color2(node) { return green2; //return ('rgb(0,0,190)'); default: - return green2; + if (is_fossil(node)) { + return ('rgb(110,110,110)'); + } else { + return green2; + } } } diff --git a/views/treeviewer/js_strings.json b/views/treeviewer/js_strings.json index d2bdc88f..68f8b336 100755 --- a/views/treeviewer/js_strings.json +++ b/views/treeviewer/js_strings.json @@ -22,8 +22,17 @@ OZstrings = { }, 'sp': T("species## singular"), 'spp': T("species## plural"), - 'Mya': T("{mya} million years ago"), - 'tya': T("{tya} thousand years ago"), + 'Fossil species':T("Fossil species"), + 'leaf_date':{ + 'Mya':T("This fossil species\nwent extinct {mya}\nmillion years ago"), + 'tya':T("This fossil species\nwent extinct {tya}\nthousand years ago"), + 'ya':T("This fossil species\nwent extinct {ya}\nyears ago") + }, + 'node_date':{ + 'Mya': T("{mya} million years ago"), + 'tya': T("{tya} thousand years ago"), + 'ya': T("{ya} years ago") + }, 'sciname': T("Scientific name: "), 'sponsor_text':{ 'node':{