choropleth-kit 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +305 -2
- package/dist/admin1/ad.d.ts +4 -0
- package/dist/admin1/ad.js +2 -0
- package/dist/admin1/ae.d.ts +4 -0
- package/dist/admin1/ae.js +2 -0
- package/dist/admin1/af.d.ts +4 -0
- package/dist/admin1/af.js +2 -0
- package/dist/admin1/ag.d.ts +4 -0
- package/dist/admin1/ag.js +2 -0
- package/dist/admin1/ai.d.ts +4 -0
- package/dist/admin1/ai.js +2 -0
- package/dist/admin1/al.d.ts +4 -0
- package/dist/admin1/al.js +2 -0
- package/dist/admin1/am.d.ts +4 -0
- package/dist/admin1/am.js +2 -0
- package/dist/admin1/ao.d.ts +4 -0
- package/dist/admin1/ao.js +2 -0
- package/dist/admin1/aq.d.ts +4 -0
- package/dist/admin1/aq.js +2 -0
- package/dist/admin1/ar.d.ts +4 -0
- package/dist/admin1/ar.js +2 -0
- package/dist/admin1/as.d.ts +4 -0
- package/dist/admin1/as.js +2 -0
- package/dist/admin1/at.d.ts +4 -0
- package/dist/admin1/at.js +2 -0
- package/dist/admin1/au.d.ts +4 -0
- package/dist/admin1/au.js +2 -0
- package/dist/admin1/aw.d.ts +4 -0
- package/dist/admin1/aw.js +2 -0
- package/dist/admin1/ax.d.ts +4 -0
- package/dist/admin1/ax.js +2 -0
- package/dist/admin1/az.d.ts +4 -0
- package/dist/admin1/az.js +2 -0
- package/dist/admin1/ba.d.ts +4 -0
- package/dist/admin1/ba.js +2 -0
- package/dist/admin1/bb.d.ts +4 -0
- package/dist/admin1/bb.js +2 -0
- package/dist/admin1/bd.d.ts +4 -0
- package/dist/admin1/bd.js +2 -0
- package/dist/admin1/be.d.ts +4 -0
- package/dist/admin1/be.js +2 -0
- package/dist/admin1/bf.d.ts +4 -0
- package/dist/admin1/bf.js +2 -0
- package/dist/admin1/bg.d.ts +4 -0
- package/dist/admin1/bg.js +2 -0
- package/dist/admin1/bh.d.ts +4 -0
- package/dist/admin1/bh.js +2 -0
- package/dist/admin1/bi.d.ts +4 -0
- package/dist/admin1/bi.js +2 -0
- package/dist/admin1/bj.d.ts +4 -0
- package/dist/admin1/bj.js +2 -0
- package/dist/admin1/bl.d.ts +4 -0
- package/dist/admin1/bl.js +2 -0
- package/dist/admin1/bm.d.ts +4 -0
- package/dist/admin1/bm.js +2 -0
- package/dist/admin1/bn.d.ts +4 -0
- package/dist/admin1/bn.js +2 -0
- package/dist/admin1/bo.d.ts +4 -0
- package/dist/admin1/bo.js +2 -0
- package/dist/admin1/br.d.ts +4 -0
- package/dist/admin1/br.js +2 -0
- package/dist/admin1/bs.d.ts +4 -0
- package/dist/admin1/bs.js +2 -0
- package/dist/admin1/bt.d.ts +4 -0
- package/dist/admin1/bt.js +2 -0
- package/dist/admin1/bw.d.ts +4 -0
- package/dist/admin1/bw.js +2 -0
- package/dist/admin1/by.d.ts +4 -0
- package/dist/admin1/by.js +2 -0
- package/dist/admin1/bz.d.ts +4 -0
- package/dist/admin1/bz.js +2 -0
- package/dist/admin1/ca.d.ts +4 -0
- package/dist/admin1/ca.js +2 -0
- package/dist/admin1/cd.d.ts +4 -0
- package/dist/admin1/cd.js +2 -0
- package/dist/admin1/cf.d.ts +4 -0
- package/dist/admin1/cf.js +2 -0
- package/dist/admin1/cg.d.ts +4 -0
- package/dist/admin1/cg.js +2 -0
- package/dist/admin1/ch.d.ts +4 -0
- package/dist/admin1/ch.js +2 -0
- package/dist/admin1/ci.d.ts +4 -0
- package/dist/admin1/ci.js +2 -0
- package/dist/admin1/ck.d.ts +4 -0
- package/dist/admin1/ck.js +2 -0
- package/dist/admin1/cl.d.ts +4 -0
- package/dist/admin1/cl.js +2 -0
- package/dist/admin1/cm.d.ts +4 -0
- package/dist/admin1/cm.js +2 -0
- package/dist/admin1/cn.d.ts +4 -0
- package/dist/admin1/cn.js +2 -0
- package/dist/admin1/co.d.ts +4 -0
- package/dist/admin1/co.js +2 -0
- package/dist/admin1/cr.d.ts +4 -0
- package/dist/admin1/cr.js +2 -0
- package/dist/admin1/cu.d.ts +4 -0
- package/dist/admin1/cu.js +2 -0
- package/dist/admin1/cv.d.ts +4 -0
- package/dist/admin1/cv.js +2 -0
- package/dist/admin1/cw.d.ts +4 -0
- package/dist/admin1/cw.js +2 -0
- package/dist/admin1/cy.d.ts +4 -0
- package/dist/admin1/cy.js +2 -0
- package/dist/admin1/cz.d.ts +4 -0
- package/dist/admin1/cz.js +2 -0
- package/dist/admin1/de.d.ts +4 -0
- package/dist/admin1/de.js +2 -0
- package/dist/admin1/dj.d.ts +4 -0
- package/dist/admin1/dj.js +2 -0
- package/dist/admin1/dk.d.ts +4 -0
- package/dist/admin1/dk.js +2 -0
- package/dist/admin1/dm.d.ts +4 -0
- package/dist/admin1/dm.js +2 -0
- package/dist/admin1/do.d.ts +4 -0
- package/dist/admin1/do.js +2 -0
- package/dist/admin1/dz.d.ts +4 -0
- package/dist/admin1/dz.js +2 -0
- package/dist/admin1/ec.d.ts +4 -0
- package/dist/admin1/ec.js +2 -0
- package/dist/admin1/ee.d.ts +4 -0
- package/dist/admin1/ee.js +2 -0
- package/dist/admin1/eg.d.ts +4 -0
- package/dist/admin1/eg.js +2 -0
- package/dist/admin1/eh.d.ts +4 -0
- package/dist/admin1/eh.js +2 -0
- package/dist/admin1/er.d.ts +4 -0
- package/dist/admin1/er.js +2 -0
- package/dist/admin1/es.d.ts +4 -0
- package/dist/admin1/es.js +2 -0
- package/dist/admin1/et.d.ts +4 -0
- package/dist/admin1/et.js +2 -0
- package/dist/admin1/fi.d.ts +4 -0
- package/dist/admin1/fi.js +2 -0
- package/dist/admin1/fj.d.ts +4 -0
- package/dist/admin1/fj.js +2 -0
- package/dist/admin1/fk.d.ts +4 -0
- package/dist/admin1/fk.js +2 -0
- package/dist/admin1/fm.d.ts +4 -0
- package/dist/admin1/fm.js +2 -0
- package/dist/admin1/fo.d.ts +4 -0
- package/dist/admin1/fo.js +2 -0
- package/dist/admin1/fr.d.ts +4 -0
- package/dist/admin1/fr.js +2 -0
- package/dist/admin1/ga.d.ts +4 -0
- package/dist/admin1/ga.js +2 -0
- package/dist/admin1/gb.d.ts +4 -0
- package/dist/admin1/gb.js +2 -0
- package/dist/admin1/gd.d.ts +4 -0
- package/dist/admin1/gd.js +2 -0
- package/dist/admin1/ge.d.ts +4 -0
- package/dist/admin1/ge.js +2 -0
- package/dist/admin1/gg.d.ts +4 -0
- package/dist/admin1/gg.js +2 -0
- package/dist/admin1/gh.d.ts +4 -0
- package/dist/admin1/gh.js +2 -0
- package/dist/admin1/gi.d.ts +4 -0
- package/dist/admin1/gi.js +2 -0
- package/dist/admin1/gl.d.ts +4 -0
- package/dist/admin1/gl.js +2 -0
- package/dist/admin1/gm.d.ts +4 -0
- package/dist/admin1/gm.js +2 -0
- package/dist/admin1/gn.d.ts +4 -0
- package/dist/admin1/gn.js +2 -0
- package/dist/admin1/gq.d.ts +4 -0
- package/dist/admin1/gq.js +2 -0
- package/dist/admin1/gr.d.ts +4 -0
- package/dist/admin1/gr.js +2 -0
- package/dist/admin1/gs.d.ts +4 -0
- package/dist/admin1/gs.js +2 -0
- package/dist/admin1/gt.d.ts +4 -0
- package/dist/admin1/gt.js +2 -0
- package/dist/admin1/gu.d.ts +4 -0
- package/dist/admin1/gu.js +2 -0
- package/dist/admin1/gw.d.ts +4 -0
- package/dist/admin1/gw.js +2 -0
- package/dist/admin1/gy.d.ts +4 -0
- package/dist/admin1/gy.js +2 -0
- package/dist/admin1/hk.d.ts +4 -0
- package/dist/admin1/hk.js +2 -0
- package/dist/admin1/hm.d.ts +4 -0
- package/dist/admin1/hm.js +2 -0
- package/dist/admin1/hn.d.ts +4 -0
- package/dist/admin1/hn.js +2 -0
- package/dist/admin1/hr.d.ts +4 -0
- package/dist/admin1/hr.js +2 -0
- package/dist/admin1/ht.d.ts +4 -0
- package/dist/admin1/ht.js +2 -0
- package/dist/admin1/hu.d.ts +4 -0
- package/dist/admin1/hu.js +2 -0
- package/dist/admin1/id.d.ts +4 -0
- package/dist/admin1/id.js +2 -0
- package/dist/admin1/ie.d.ts +4 -0
- package/dist/admin1/ie.js +2 -0
- package/dist/admin1/il.d.ts +4 -0
- package/dist/admin1/il.js +2 -0
- package/dist/admin1/im.d.ts +4 -0
- package/dist/admin1/im.js +2 -0
- package/dist/admin1/in.d.ts +4 -0
- package/dist/admin1/in.js +2 -0
- package/dist/admin1/io.d.ts +4 -0
- package/dist/admin1/io.js +2 -0
- package/dist/admin1/iq.d.ts +4 -0
- package/dist/admin1/iq.js +2 -0
- package/dist/admin1/ir.d.ts +4 -0
- package/dist/admin1/ir.js +2 -0
- package/dist/admin1/is.d.ts +4 -0
- package/dist/admin1/is.js +2 -0
- package/dist/admin1/it.d.ts +4 -0
- package/dist/admin1/it.js +2 -0
- package/dist/admin1/je.d.ts +4 -0
- package/dist/admin1/je.js +2 -0
- package/dist/admin1/jm.d.ts +4 -0
- package/dist/admin1/jm.js +2 -0
- package/dist/admin1/jo.d.ts +4 -0
- package/dist/admin1/jo.js +2 -0
- package/dist/admin1/jp.d.ts +4 -0
- package/dist/admin1/jp.js +2 -0
- package/dist/admin1/ke.d.ts +4 -0
- package/dist/admin1/ke.js +2 -0
- package/dist/admin1/kg.d.ts +4 -0
- package/dist/admin1/kg.js +2 -0
- package/dist/admin1/kh.d.ts +4 -0
- package/dist/admin1/kh.js +2 -0
- package/dist/admin1/ki.d.ts +4 -0
- package/dist/admin1/ki.js +2 -0
- package/dist/admin1/km.d.ts +4 -0
- package/dist/admin1/km.js +2 -0
- package/dist/admin1/kn.d.ts +4 -0
- package/dist/admin1/kn.js +2 -0
- package/dist/admin1/kp.d.ts +4 -0
- package/dist/admin1/kp.js +2 -0
- package/dist/admin1/kr.d.ts +4 -0
- package/dist/admin1/kr.js +2 -0
- package/dist/admin1/kw.d.ts +4 -0
- package/dist/admin1/kw.js +2 -0
- package/dist/admin1/ky.d.ts +4 -0
- package/dist/admin1/ky.js +2 -0
- package/dist/admin1/kz.d.ts +4 -0
- package/dist/admin1/kz.js +2 -0
- package/dist/admin1/la.d.ts +4 -0
- package/dist/admin1/la.js +2 -0
- package/dist/admin1/lb.d.ts +4 -0
- package/dist/admin1/lb.js +2 -0
- package/dist/admin1/lc.d.ts +4 -0
- package/dist/admin1/lc.js +2 -0
- package/dist/admin1/li.d.ts +4 -0
- package/dist/admin1/li.js +2 -0
- package/dist/admin1/lk.d.ts +4 -0
- package/dist/admin1/lk.js +2 -0
- package/dist/admin1/lr.d.ts +4 -0
- package/dist/admin1/lr.js +2 -0
- package/dist/admin1/ls.d.ts +4 -0
- package/dist/admin1/ls.js +2 -0
- package/dist/admin1/lt.d.ts +4 -0
- package/dist/admin1/lt.js +2 -0
- package/dist/admin1/lu.d.ts +4 -0
- package/dist/admin1/lu.js +2 -0
- package/dist/admin1/lv.d.ts +4 -0
- package/dist/admin1/lv.js +2 -0
- package/dist/admin1/ly.d.ts +4 -0
- package/dist/admin1/ly.js +2 -0
- package/dist/admin1/ma.d.ts +4 -0
- package/dist/admin1/ma.js +2 -0
- package/dist/admin1/mc.d.ts +4 -0
- package/dist/admin1/mc.js +2 -0
- package/dist/admin1/md.d.ts +4 -0
- package/dist/admin1/md.js +2 -0
- package/dist/admin1/me.d.ts +4 -0
- package/dist/admin1/me.js +2 -0
- package/dist/admin1/mf.d.ts +4 -0
- package/dist/admin1/mf.js +2 -0
- package/dist/admin1/mg.d.ts +4 -0
- package/dist/admin1/mg.js +2 -0
- package/dist/admin1/mh.d.ts +4 -0
- package/dist/admin1/mh.js +2 -0
- package/dist/admin1/mk.d.ts +4 -0
- package/dist/admin1/mk.js +2 -0
- package/dist/admin1/ml.d.ts +4 -0
- package/dist/admin1/ml.js +2 -0
- package/dist/admin1/mm.d.ts +4 -0
- package/dist/admin1/mm.js +2 -0
- package/dist/admin1/mn.d.ts +4 -0
- package/dist/admin1/mn.js +2 -0
- package/dist/admin1/mo.d.ts +4 -0
- package/dist/admin1/mo.js +2 -0
- package/dist/admin1/mp.d.ts +4 -0
- package/dist/admin1/mp.js +2 -0
- package/dist/admin1/mr.d.ts +4 -0
- package/dist/admin1/mr.js +2 -0
- package/dist/admin1/ms.d.ts +4 -0
- package/dist/admin1/ms.js +2 -0
- package/dist/admin1/mt.d.ts +4 -0
- package/dist/admin1/mt.js +2 -0
- package/dist/admin1/mu.d.ts +4 -0
- package/dist/admin1/mu.js +2 -0
- package/dist/admin1/mv.d.ts +4 -0
- package/dist/admin1/mv.js +2 -0
- package/dist/admin1/mw.d.ts +4 -0
- package/dist/admin1/mw.js +2 -0
- package/dist/admin1/mx.d.ts +4 -0
- package/dist/admin1/mx.js +2 -0
- package/dist/admin1/my.d.ts +4 -0
- package/dist/admin1/my.js +2 -0
- package/dist/admin1/mz.d.ts +4 -0
- package/dist/admin1/mz.js +2 -0
- package/dist/admin1/na.d.ts +4 -0
- package/dist/admin1/na.js +2 -0
- package/dist/admin1/nc.d.ts +4 -0
- package/dist/admin1/nc.js +2 -0
- package/dist/admin1/ne.d.ts +4 -0
- package/dist/admin1/ne.js +2 -0
- package/dist/admin1/nf.d.ts +4 -0
- package/dist/admin1/nf.js +2 -0
- package/dist/admin1/ng.d.ts +4 -0
- package/dist/admin1/ng.js +2 -0
- package/dist/admin1/ni.d.ts +4 -0
- package/dist/admin1/ni.js +2 -0
- package/dist/admin1/nl.d.ts +4 -0
- package/dist/admin1/nl.js +2 -0
- package/dist/admin1/no.d.ts +4 -0
- package/dist/admin1/no.js +2 -0
- package/dist/admin1/np.d.ts +4 -0
- package/dist/admin1/np.js +2 -0
- package/dist/admin1/nr.d.ts +4 -0
- package/dist/admin1/nr.js +2 -0
- package/dist/admin1/nu.d.ts +4 -0
- package/dist/admin1/nu.js +2 -0
- package/dist/admin1/nz.d.ts +4 -0
- package/dist/admin1/nz.js +2 -0
- package/dist/admin1/om.d.ts +4 -0
- package/dist/admin1/om.js +2 -0
- package/dist/admin1/pa.d.ts +4 -0
- package/dist/admin1/pa.js +2 -0
- package/dist/admin1/pe.d.ts +4 -0
- package/dist/admin1/pe.js +2 -0
- package/dist/admin1/pf.d.ts +4 -0
- package/dist/admin1/pf.js +2 -0
- package/dist/admin1/pg.d.ts +4 -0
- package/dist/admin1/pg.js +2 -0
- package/dist/admin1/ph.d.ts +4 -0
- package/dist/admin1/ph.js +2 -0
- package/dist/admin1/pk.d.ts +4 -0
- package/dist/admin1/pk.js +2 -0
- package/dist/admin1/pl.d.ts +4 -0
- package/dist/admin1/pl.js +2 -0
- package/dist/admin1/pm.d.ts +4 -0
- package/dist/admin1/pm.js +2 -0
- package/dist/admin1/pn.d.ts +4 -0
- package/dist/admin1/pn.js +2 -0
- package/dist/admin1/ps.d.ts +4 -0
- package/dist/admin1/ps.js +2 -0
- package/dist/admin1/pt.d.ts +4 -0
- package/dist/admin1/pt.js +2 -0
- package/dist/admin1/pw.d.ts +4 -0
- package/dist/admin1/pw.js +2 -0
- package/dist/admin1/py.d.ts +4 -0
- package/dist/admin1/py.js +2 -0
- package/dist/admin1/qa.d.ts +4 -0
- package/dist/admin1/qa.js +2 -0
- package/dist/admin1/ro.d.ts +4 -0
- package/dist/admin1/ro.js +2 -0
- package/dist/admin1/rs.d.ts +4 -0
- package/dist/admin1/rs.js +2 -0
- package/dist/admin1/ru.d.ts +4 -0
- package/dist/admin1/ru.js +2 -0
- package/dist/admin1/rw.d.ts +4 -0
- package/dist/admin1/rw.js +2 -0
- package/dist/admin1/sa.d.ts +4 -0
- package/dist/admin1/sa.js +2 -0
- package/dist/admin1/sb.d.ts +4 -0
- package/dist/admin1/sb.js +2 -0
- package/dist/admin1/sc.d.ts +4 -0
- package/dist/admin1/sc.js +2 -0
- package/dist/admin1/sd.d.ts +4 -0
- package/dist/admin1/sd.js +2 -0
- package/dist/admin1/se.d.ts +4 -0
- package/dist/admin1/se.js +2 -0
- package/dist/admin1/sg.d.ts +4 -0
- package/dist/admin1/sg.js +2 -0
- package/dist/admin1/sh.d.ts +4 -0
- package/dist/admin1/sh.js +2 -0
- package/dist/admin1/si.d.ts +4 -0
- package/dist/admin1/si.js +2 -0
- package/dist/admin1/sk.d.ts +4 -0
- package/dist/admin1/sk.js +2 -0
- package/dist/admin1/sl.d.ts +4 -0
- package/dist/admin1/sl.js +2 -0
- package/dist/admin1/sm.d.ts +4 -0
- package/dist/admin1/sm.js +2 -0
- package/dist/admin1/sn.d.ts +4 -0
- package/dist/admin1/sn.js +2 -0
- package/dist/admin1/so.d.ts +4 -0
- package/dist/admin1/so.js +2 -0
- package/dist/admin1/sr.d.ts +4 -0
- package/dist/admin1/sr.js +2 -0
- package/dist/admin1/ss.d.ts +4 -0
- package/dist/admin1/ss.js +2 -0
- package/dist/admin1/st.d.ts +4 -0
- package/dist/admin1/st.js +2 -0
- package/dist/admin1/sv.d.ts +4 -0
- package/dist/admin1/sv.js +2 -0
- package/dist/admin1/sy.d.ts +4 -0
- package/dist/admin1/sy.js +2 -0
- package/dist/admin1/sz.d.ts +4 -0
- package/dist/admin1/sz.js +2 -0
- package/dist/admin1/tc.d.ts +4 -0
- package/dist/admin1/tc.js +2 -0
- package/dist/admin1/td.d.ts +4 -0
- package/dist/admin1/td.js +2 -0
- package/dist/admin1/tf.d.ts +4 -0
- package/dist/admin1/tf.js +2 -0
- package/dist/admin1/tg.d.ts +4 -0
- package/dist/admin1/tg.js +2 -0
- package/dist/admin1/th.d.ts +4 -0
- package/dist/admin1/th.js +2 -0
- package/dist/admin1/tj.d.ts +4 -0
- package/dist/admin1/tj.js +2 -0
- package/dist/admin1/tk.d.ts +4 -0
- package/dist/admin1/tk.js +2 -0
- package/dist/admin1/tl.d.ts +4 -0
- package/dist/admin1/tl.js +2 -0
- package/dist/admin1/tm.d.ts +4 -0
- package/dist/admin1/tm.js +2 -0
- package/dist/admin1/tn.d.ts +4 -0
- package/dist/admin1/tn.js +2 -0
- package/dist/admin1/to.d.ts +4 -0
- package/dist/admin1/to.js +2 -0
- package/dist/admin1/tr.d.ts +4 -0
- package/dist/admin1/tr.js +2 -0
- package/dist/admin1/tt.d.ts +4 -0
- package/dist/admin1/tt.js +2 -0
- package/dist/admin1/tv.d.ts +4 -0
- package/dist/admin1/tv.js +2 -0
- package/dist/admin1/tw.d.ts +4 -0
- package/dist/admin1/tw.js +2 -0
- package/dist/admin1/tz.d.ts +4 -0
- package/dist/admin1/tz.js +2 -0
- package/dist/admin1/ua.d.ts +4 -0
- package/dist/admin1/ua.js +2 -0
- package/dist/admin1/ug.d.ts +4 -0
- package/dist/admin1/ug.js +2 -0
- package/dist/admin1/um.d.ts +4 -0
- package/dist/admin1/um.js +2 -0
- package/dist/admin1/us.d.ts +4 -0
- package/dist/admin1/us.js +2 -0
- package/dist/admin1/uy.d.ts +4 -0
- package/dist/admin1/uy.js +2 -0
- package/dist/admin1/uz.d.ts +4 -0
- package/dist/admin1/uz.js +2 -0
- package/dist/admin1/va.d.ts +4 -0
- package/dist/admin1/va.js +2 -0
- package/dist/admin1/vc.d.ts +4 -0
- package/dist/admin1/vc.js +2 -0
- package/dist/admin1/ve.d.ts +4 -0
- package/dist/admin1/ve.js +2 -0
- package/dist/admin1/vg.d.ts +4 -0
- package/dist/admin1/vg.js +2 -0
- package/dist/admin1/vi.d.ts +4 -0
- package/dist/admin1/vi.js +2 -0
- package/dist/admin1/vn.d.ts +4 -0
- package/dist/admin1/vn.js +2 -0
- package/dist/admin1/vu.d.ts +4 -0
- package/dist/admin1/vu.js +2 -0
- package/dist/admin1/wf.d.ts +4 -0
- package/dist/admin1/wf.js +2 -0
- package/dist/admin1/ws.d.ts +4 -0
- package/dist/admin1/ws.js +2 -0
- package/dist/admin1/xk.d.ts +4 -0
- package/dist/admin1/xk.js +2 -0
- package/dist/admin1/ye.d.ts +4 -0
- package/dist/admin1/ye.js +2 -0
- package/dist/admin1/za.d.ts +4 -0
- package/dist/admin1/za.js +2 -0
- package/dist/admin1/zm.d.ts +4 -0
- package/dist/admin1/zm.js +2 -0
- package/dist/admin1/zw.d.ts +4 -0
- package/dist/admin1/zw.js +2 -0
- package/dist/chunk-ZN6SLXL3.js +1932 -0
- package/dist/core.d.ts +173 -0
- package/dist/core.js +203 -0
- package/dist/drill-chain-C8hhHBnd.d.ts +1256 -0
- package/dist/geo.d.ts +89 -0
- package/dist/geo.js +457 -0
- package/dist/index.d.ts +1256 -0
- package/dist/index.js +3110 -0
- package/dist/styles.css +417 -0
- package/dist/styles.d.ts +2 -0
- package/package.json +147 -3
package/dist/index.js
ADDED
|
@@ -0,0 +1,3110 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_EMPTY_FILL,
|
|
3
|
+
DEFAULT_FLIGHT_CURVATURE,
|
|
4
|
+
DEFAULT_FOCUS_BOUNDS,
|
|
5
|
+
FIT_TRANSFORM,
|
|
6
|
+
INERTIA_TAU_MS,
|
|
7
|
+
THEMES,
|
|
8
|
+
bubbleScale,
|
|
9
|
+
buildBreadcrumbs,
|
|
10
|
+
buildProjectionChain,
|
|
11
|
+
checkDrillStep,
|
|
12
|
+
clampPath,
|
|
13
|
+
classifyDrill,
|
|
14
|
+
computeAspectRatio,
|
|
15
|
+
constrainTransform,
|
|
16
|
+
contextOf,
|
|
17
|
+
createBorders,
|
|
18
|
+
createDrillState,
|
|
19
|
+
createIntentTimer,
|
|
20
|
+
createLruCache,
|
|
21
|
+
createProjection,
|
|
22
|
+
createZoomFilter,
|
|
23
|
+
createZoomInterpolator,
|
|
24
|
+
defaultFormatValue,
|
|
25
|
+
defineTheme,
|
|
26
|
+
drillDirectionBetween,
|
|
27
|
+
drillReducer,
|
|
28
|
+
dropOverseas,
|
|
29
|
+
extendTheme,
|
|
30
|
+
flightDuration,
|
|
31
|
+
formatDrillRequestProblem,
|
|
32
|
+
formatJoinDiagnostics,
|
|
33
|
+
formatNoIdsDiagnostic,
|
|
34
|
+
formatTooDeepRequest,
|
|
35
|
+
formatWrongLevelDataHint,
|
|
36
|
+
geographyKeyFor,
|
|
37
|
+
getAnchorPoint,
|
|
38
|
+
getFocusExtent,
|
|
39
|
+
getFocusGeometry,
|
|
40
|
+
inertiaOffset,
|
|
41
|
+
isFitTransform,
|
|
42
|
+
isThemeName,
|
|
43
|
+
joinData,
|
|
44
|
+
preserveView,
|
|
45
|
+
regionRotation,
|
|
46
|
+
relativeTransform,
|
|
47
|
+
releaseVelocity,
|
|
48
|
+
resolveControls,
|
|
49
|
+
resolveDrillData,
|
|
50
|
+
resolveLabels,
|
|
51
|
+
resolveRegion,
|
|
52
|
+
resolveTheme,
|
|
53
|
+
resolveVariant,
|
|
54
|
+
resolveZoomOptions,
|
|
55
|
+
resolveZoomTarget,
|
|
56
|
+
savedCameraFor,
|
|
57
|
+
themeToCssVars,
|
|
58
|
+
toFeatureCollection,
|
|
59
|
+
toViewState,
|
|
60
|
+
unwrapGeography,
|
|
61
|
+
validateLevels,
|
|
62
|
+
zoomKeyAction
|
|
63
|
+
} from "./chunk-ZN6SLXL3.js";
|
|
64
|
+
|
|
65
|
+
// src/react/use-choropleth.ts
|
|
66
|
+
import {
|
|
67
|
+
useEffect as useEffect6,
|
|
68
|
+
useLayoutEffect,
|
|
69
|
+
useMemo as useMemo4,
|
|
70
|
+
useRef as useRef5,
|
|
71
|
+
useState as useState5
|
|
72
|
+
} from "react";
|
|
73
|
+
import { geoPath } from "d3-geo";
|
|
74
|
+
|
|
75
|
+
// src/react/drill-levels.ts
|
|
76
|
+
var defineDrillLevels = (levels) => {
|
|
77
|
+
validateLevels(levels);
|
|
78
|
+
return levels;
|
|
79
|
+
};
|
|
80
|
+
var buildDrillEvent = ({
|
|
81
|
+
from,
|
|
82
|
+
to,
|
|
83
|
+
item
|
|
84
|
+
}) => {
|
|
85
|
+
const direction = drillDirectionBetween(from, to);
|
|
86
|
+
if (direction === null) return null;
|
|
87
|
+
return {
|
|
88
|
+
direction,
|
|
89
|
+
from: contextOf(from),
|
|
90
|
+
to: contextOf(to),
|
|
91
|
+
...item === void 0 ? {} : { item }
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
var isDrillable = (levels, depth, item) => {
|
|
95
|
+
const level = levels[depth];
|
|
96
|
+
if (level === void 0 || depth >= levels.length - 1) return false;
|
|
97
|
+
const { canDrill } = level;
|
|
98
|
+
if (canDrill === void 0) return true;
|
|
99
|
+
return typeof canDrill === "function" ? canDrill(item) : canDrill;
|
|
100
|
+
};
|
|
101
|
+
var sourceDepth = (levels, depth) => {
|
|
102
|
+
let source = Math.min(depth, levels.length - 1);
|
|
103
|
+
while (source > 0 && levels[source]?.geography === "inherit") source--;
|
|
104
|
+
return source;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// src/react/use-drill-choreography.ts
|
|
108
|
+
import { useEffect, useRef } from "react";
|
|
109
|
+
|
|
110
|
+
// src/react/env.ts
|
|
111
|
+
var isProduction = () => {
|
|
112
|
+
try {
|
|
113
|
+
return process.env.NODE_ENV === "production";
|
|
114
|
+
} catch {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
var prefersReducedMotion = () => typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
119
|
+
|
|
120
|
+
// src/react/use-drill-choreography.ts
|
|
121
|
+
var REVEAL_MS = 450;
|
|
122
|
+
var lastOf = (items) => items[items.length - 1];
|
|
123
|
+
var useDrillChoreography = ({
|
|
124
|
+
engine,
|
|
125
|
+
store,
|
|
126
|
+
shownChain,
|
|
127
|
+
chainFor
|
|
128
|
+
}) => {
|
|
129
|
+
const { active, machine, dispatch } = engine;
|
|
130
|
+
const latest = useRef({ engine, shownChain, chainFor });
|
|
131
|
+
useEffect(() => {
|
|
132
|
+
latest.current = { engine, shownChain, chainFor };
|
|
133
|
+
});
|
|
134
|
+
const flying = machine.phase === "flying";
|
|
135
|
+
const flightId = machine.flightId;
|
|
136
|
+
useEffect(() => {
|
|
137
|
+
if (!active || !flying) return;
|
|
138
|
+
const { engine: current, shownChain: shown, chainFor: build } = latest.current;
|
|
139
|
+
const state = current.machine;
|
|
140
|
+
const finish = (completed) => dispatch({ type: "FLIGHT_END", id: flightId, completed });
|
|
141
|
+
let destination;
|
|
142
|
+
if (state.direction === "up") {
|
|
143
|
+
destination = state.returnCamera ?? savedCameraFor(state, state.path) ?? FIT_TRANSFORM;
|
|
144
|
+
} else {
|
|
145
|
+
const levels = current.chainLevelsFor(state.path);
|
|
146
|
+
const next = levels === null ? null : build(levels, state.path);
|
|
147
|
+
const from = lastOf(shown?.projections ?? []);
|
|
148
|
+
const to = lastOf(next?.projections ?? []);
|
|
149
|
+
destination = from === void 0 || to === void 0 || next?.missing != null ? null : relativeTransform(from, to);
|
|
150
|
+
}
|
|
151
|
+
const api = store.getZoom();
|
|
152
|
+
if (api === null || destination === null) {
|
|
153
|
+
finish(true);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const flight = { cancelled: false };
|
|
157
|
+
void api.flyTo(destination).then(({ completed }) => {
|
|
158
|
+
if (!flight.cancelled) finish(completed);
|
|
159
|
+
});
|
|
160
|
+
return () => {
|
|
161
|
+
flight.cancelled = true;
|
|
162
|
+
};
|
|
163
|
+
}, [active, flying, flightId, store, dispatch]);
|
|
164
|
+
const revealing = machine.phase === "revealing";
|
|
165
|
+
useEffect(() => {
|
|
166
|
+
if (!active || !revealing) return;
|
|
167
|
+
const timer = setTimeout(
|
|
168
|
+
() => dispatch({ type: "REVEAL_END" }),
|
|
169
|
+
prefersReducedMotion() ? 0 : REVEAL_MS
|
|
170
|
+
);
|
|
171
|
+
return () => clearTimeout(timer);
|
|
172
|
+
}, [active, revealing, dispatch]);
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// src/react/map-item.ts
|
|
176
|
+
var ROOT_LEVEL = { id: "root", depth: 0 };
|
|
177
|
+
var isSameItem = (a, b) => a === b || a !== null && b !== null && a.kind === b.kind && a.id === b.id;
|
|
178
|
+
|
|
179
|
+
// src/react/map-store.ts
|
|
180
|
+
import { useSyncExternalStore } from "react";
|
|
181
|
+
var GESTURE_HINT_MS = 1500;
|
|
182
|
+
var INITIAL_STATE = {
|
|
183
|
+
hovered: null,
|
|
184
|
+
pointer: null,
|
|
185
|
+
pointerType: "mouse",
|
|
186
|
+
interacting: false,
|
|
187
|
+
camera: FIT_TRANSFORM,
|
|
188
|
+
gestureHint: null
|
|
189
|
+
};
|
|
190
|
+
var createMapStore = () => {
|
|
191
|
+
let state = INITIAL_STATE;
|
|
192
|
+
let hintTimer;
|
|
193
|
+
let zoomApi = null;
|
|
194
|
+
let drillApi = null;
|
|
195
|
+
let fallbackView = null;
|
|
196
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
197
|
+
const store = {
|
|
198
|
+
subscribe: (listener) => {
|
|
199
|
+
listeners.add(listener);
|
|
200
|
+
return () => listeners.delete(listener);
|
|
201
|
+
},
|
|
202
|
+
getState: () => state,
|
|
203
|
+
setState: (patch) => {
|
|
204
|
+
const changed = Object.keys(patch).some(
|
|
205
|
+
(key) => !Object.is(state[key], patch[key])
|
|
206
|
+
);
|
|
207
|
+
if (!changed) return;
|
|
208
|
+
state = { ...state, ...patch };
|
|
209
|
+
for (const listener of listeners) listener();
|
|
210
|
+
},
|
|
211
|
+
showGestureHint: (kind) => {
|
|
212
|
+
clearTimeout(hintTimer);
|
|
213
|
+
store.setState({ gestureHint: kind });
|
|
214
|
+
hintTimer = setTimeout(() => store.setState({ gestureHint: null }), GESTURE_HINT_MS);
|
|
215
|
+
},
|
|
216
|
+
dispose: () => clearTimeout(hintTimer),
|
|
217
|
+
registerZoom: (api) => {
|
|
218
|
+
zoomApi = api;
|
|
219
|
+
},
|
|
220
|
+
getZoom: () => zoomApi,
|
|
221
|
+
registerDrill: (api) => {
|
|
222
|
+
drillApi = api;
|
|
223
|
+
},
|
|
224
|
+
getDrill: () => drillApi,
|
|
225
|
+
registerFallbackView: (read) => {
|
|
226
|
+
fallbackView = read;
|
|
227
|
+
},
|
|
228
|
+
getFallbackView: () => fallbackView?.() ?? null
|
|
229
|
+
};
|
|
230
|
+
return store;
|
|
231
|
+
};
|
|
232
|
+
var useMapStore = (store, selector) => useSyncExternalStore(
|
|
233
|
+
store.subscribe,
|
|
234
|
+
() => selector(store.getState()),
|
|
235
|
+
() => selector(INITIAL_STATE)
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
// src/react/use-container-size.ts
|
|
239
|
+
import { useEffect as useEffect2, useRef as useRef2, useState } from "react";
|
|
240
|
+
var DEFAULT_WIDTH = 960;
|
|
241
|
+
var DEFAULT_ASPECT_RATIO = 960 / 500;
|
|
242
|
+
var useContainerSize = ({
|
|
243
|
+
initial,
|
|
244
|
+
aspectRatio = DEFAULT_ASPECT_RATIO,
|
|
245
|
+
useContainerHeight = false
|
|
246
|
+
} = {}) => {
|
|
247
|
+
const ref = useRef2(null);
|
|
248
|
+
const [measured, setMeasured] = useState(null);
|
|
249
|
+
useEffect2(() => {
|
|
250
|
+
const element = ref.current;
|
|
251
|
+
if (element === null || typeof ResizeObserver === "undefined") return;
|
|
252
|
+
const observer = new ResizeObserver(([entry]) => {
|
|
253
|
+
if (entry === void 0) return;
|
|
254
|
+
const { width, height: height2 } = entry.contentRect;
|
|
255
|
+
if (!(width > 0)) return;
|
|
256
|
+
setMeasured(
|
|
257
|
+
(prev) => prev?.width === width && prev.height === height2 ? prev : { width, height: height2 }
|
|
258
|
+
);
|
|
259
|
+
});
|
|
260
|
+
observer.observe(element);
|
|
261
|
+
return () => observer.disconnect();
|
|
262
|
+
}, []);
|
|
263
|
+
if (measured === null) {
|
|
264
|
+
return { ref, ...initial ?? { width: DEFAULT_WIDTH, height: DEFAULT_WIDTH / aspectRatio } };
|
|
265
|
+
}
|
|
266
|
+
const height = useContainerHeight && measured.height > 0 ? measured.height : measured.width / aspectRatio;
|
|
267
|
+
return { ref, width: measured.width, height };
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
// src/react/use-drill-engine.ts
|
|
271
|
+
import {
|
|
272
|
+
useCallback as useCallback2,
|
|
273
|
+
useEffect as useEffect4,
|
|
274
|
+
useMemo as useMemo3,
|
|
275
|
+
useReducer,
|
|
276
|
+
useRef as useRef4,
|
|
277
|
+
useState as useState3
|
|
278
|
+
} from "react";
|
|
279
|
+
|
|
280
|
+
// src/react/region.ts
|
|
281
|
+
import { useMemo } from "react";
|
|
282
|
+
|
|
283
|
+
// src/geo/overseas.ts
|
|
284
|
+
var OVERSEAS_TERRITORIES = {
|
|
285
|
+
// French Guiana.
|
|
286
|
+
FR: [
|
|
287
|
+
[
|
|
288
|
+
[-56, 1],
|
|
289
|
+
[-51, 7]
|
|
290
|
+
]
|
|
291
|
+
],
|
|
292
|
+
// Svalbard.
|
|
293
|
+
NO: [
|
|
294
|
+
[
|
|
295
|
+
[0, 73],
|
|
296
|
+
[40, 82]
|
|
297
|
+
]
|
|
298
|
+
],
|
|
299
|
+
// Hawaii.
|
|
300
|
+
US: [
|
|
301
|
+
[
|
|
302
|
+
[-162, 18],
|
|
303
|
+
[-154, 23]
|
|
304
|
+
]
|
|
305
|
+
]
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
// src/geo/region-presets.ts
|
|
309
|
+
var ids = (list) => list.split(" ");
|
|
310
|
+
var northernEurope = ids("AX DK EE FI FO GB GG IE IM IS JE LT LV NO SE");
|
|
311
|
+
var westernEurope = ids("AT BE CH DE FR LI LU MC NL");
|
|
312
|
+
var southernEurope = ids("AD AL BA ES GI GR HR IT ME MK MT PT RS SI SM VA XK");
|
|
313
|
+
var easternEurope = ids("BG BY CZ HU MD PL RO SK UA");
|
|
314
|
+
var northernAfrica = ids("DZ EG EH LY MA SD TN");
|
|
315
|
+
var easternAfrica = ids("BI DJ ER ET KE KM MG MU MW MZ RW SC SO SS TZ UG ZM ZW");
|
|
316
|
+
var middleAfrica = ids("AO CD CF CG CM GA GQ ST TD");
|
|
317
|
+
var southernAfrica = ids("BW LS NA SZ ZA");
|
|
318
|
+
var westernAfrica = ids("BF BJ CI CV GH GM GN GW LR ML MR NE NG SL SN TG");
|
|
319
|
+
var centralAsia = ids("KG KZ TJ TM UZ");
|
|
320
|
+
var easternAsia = ids("CN HK JP KP KR MN MO TW");
|
|
321
|
+
var southeasternAsia = ids("BN ID KH LA MM MY PH SG TH TL VN");
|
|
322
|
+
var southernAsia = ids("AF BD BT IN IR LK MV NP PK");
|
|
323
|
+
var westernAsia = ids("AE AM AZ BH CY GE IL IQ JO KW LB OM PS QA SA SY TR YE");
|
|
324
|
+
var usAndCanada = ids("BM CA GL US");
|
|
325
|
+
var centralAmerica = ids("BZ CR GT HN MX NI PA SV");
|
|
326
|
+
var caribbean = ids("AG AW BB BS CU CW DM DO GD HT JM KN KY LC PR TT VC");
|
|
327
|
+
var southAmerica = ids("AR BO BR CL CO EC FK GY PE PY SR UY VE");
|
|
328
|
+
var australiaAndNewZealand = ids("AU NZ");
|
|
329
|
+
var melanesia = ids("FJ NC PG SB VU");
|
|
330
|
+
var micronesia = ids("FM GU KI MH NR PW");
|
|
331
|
+
var polynesia = ids("PF TO TV WS");
|
|
332
|
+
var REGION_PRESETS = {
|
|
333
|
+
// Continents: disjoint, each one is the union of its subregions.
|
|
334
|
+
africa: [
|
|
335
|
+
...northernAfrica,
|
|
336
|
+
...easternAfrica,
|
|
337
|
+
...middleAfrica,
|
|
338
|
+
...southernAfrica,
|
|
339
|
+
...westernAfrica
|
|
340
|
+
],
|
|
341
|
+
asia: [...centralAsia, ...easternAsia, ...southeasternAsia, ...southernAsia, ...westernAsia],
|
|
342
|
+
europe: [...northernEurope, ...westernEurope, ...southernEurope, ...easternEurope],
|
|
343
|
+
northAmerica: [...usAndCanada, ...centralAmerica, ...caribbean],
|
|
344
|
+
southAmerica,
|
|
345
|
+
oceania: [...australiaAndNewZealand, ...melanesia, ...micronesia, ...polynesia],
|
|
346
|
+
// Subregions (UN geoscheme).
|
|
347
|
+
northernAfrica,
|
|
348
|
+
easternAfrica,
|
|
349
|
+
middleAfrica,
|
|
350
|
+
southernAfrica,
|
|
351
|
+
westernAfrica,
|
|
352
|
+
centralAsia,
|
|
353
|
+
easternAsia,
|
|
354
|
+
southeasternAsia,
|
|
355
|
+
southernAsia,
|
|
356
|
+
westernAsia,
|
|
357
|
+
northernEurope,
|
|
358
|
+
westernEurope,
|
|
359
|
+
southernEurope,
|
|
360
|
+
easternEurope,
|
|
361
|
+
usAndCanada,
|
|
362
|
+
centralAmerica,
|
|
363
|
+
caribbean,
|
|
364
|
+
australiaAndNewZealand,
|
|
365
|
+
melanesia,
|
|
366
|
+
micronesia,
|
|
367
|
+
polynesia,
|
|
368
|
+
// Political / commercial groupings: they may overlap with everything else. FIRST CUT, to be confirmed with
|
|
369
|
+
// the tools that will use them (see FEAT-022); each one is a maintenance cost.
|
|
370
|
+
eu27: ids("AT BE BG CY CZ DE DK EE ES FI FR GR HR HU IE IT LT LU LV MT NL PL PT RO SE SI SK"),
|
|
371
|
+
nordics: ids("DK FI IS NO SE"),
|
|
372
|
+
iberia: ids("ES PT"),
|
|
373
|
+
latam: ids("AR BO BR CL CO CR CU DO EC GT HN HT MX NI PA PE PR PY SV UY VE"),
|
|
374
|
+
mena: ids("AE BH DZ EG EH IL IQ IR JO KW LB LY MA OM PS QA SA SY TN YE")
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
// src/react/region.ts
|
|
378
|
+
var regionKey = (region) => typeof region === "string" ? region : region.join("\0");
|
|
379
|
+
var useRegion = (region) => {
|
|
380
|
+
const key = region === void 0 ? void 0 : regionKey(region);
|
|
381
|
+
return useMemo(
|
|
382
|
+
() => region === void 0 || key === void 0 ? void 0 : { key, ids: resolveRegion(region, REGION_PRESETS) },
|
|
383
|
+
// `key` summarizes the content of `region`.
|
|
384
|
+
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
|
385
|
+
[key]
|
|
386
|
+
);
|
|
387
|
+
};
|
|
388
|
+
var cache = /* @__PURE__ */ new WeakMap();
|
|
389
|
+
var withoutOverseas = (fc, region) => {
|
|
390
|
+
const byKey = cache.get(fc) ?? /* @__PURE__ */ new Map();
|
|
391
|
+
cache.set(fc, byKey);
|
|
392
|
+
const found = byKey.get(region.key);
|
|
393
|
+
if (found !== void 0) return found;
|
|
394
|
+
const features = dropOverseas(fc.features, OVERSEAS_TERRITORIES);
|
|
395
|
+
const created = features.every((feature, i) => feature === fc.features[i]) ? fc : { ...fc, features };
|
|
396
|
+
byKey.set(region.key, created);
|
|
397
|
+
return created;
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
// src/react/use-drill-down.ts
|
|
401
|
+
import { useCallback, useEffect as useEffect3, useMemo as useMemo2, useRef as useRef3, useState as useState2, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
402
|
+
var createDrillBridge = () => {
|
|
403
|
+
let status = "idle";
|
|
404
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
405
|
+
return {
|
|
406
|
+
getStatus: () => status,
|
|
407
|
+
setStatus: (next) => {
|
|
408
|
+
if (next === status) return;
|
|
409
|
+
status = next;
|
|
410
|
+
listeners.forEach((listener) => listener());
|
|
411
|
+
},
|
|
412
|
+
subscribe: (listener) => {
|
|
413
|
+
listeners.add(listener);
|
|
414
|
+
return () => void listeners.delete(listener);
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
};
|
|
418
|
+
var DRILL_BRIDGE = /* @__PURE__ */ Symbol("choropleth.drillBridge");
|
|
419
|
+
var samePath = (a, b) => a.length === b.length && a.every((id, i) => id === b[i]);
|
|
420
|
+
var warn = (message) => {
|
|
421
|
+
if (!isProduction()) console.warn(`[choropleth] useDrillDown: ${message}`);
|
|
422
|
+
};
|
|
423
|
+
var useDrillDown = ({
|
|
424
|
+
levels,
|
|
425
|
+
path: controlledPath,
|
|
426
|
+
defaultPath,
|
|
427
|
+
onPathChange,
|
|
428
|
+
getLabel
|
|
429
|
+
}) => {
|
|
430
|
+
useMemo2(() => validateLevels(levels), [levels]);
|
|
431
|
+
const controlled = controlledPath !== void 0;
|
|
432
|
+
const [innerPath, setInnerPath] = useState2(
|
|
433
|
+
() => clampPath(defaultPath ?? [], levels.length).path
|
|
434
|
+
);
|
|
435
|
+
const [bridge] = useState2(createDrillBridge);
|
|
436
|
+
const requested = controlled ? controlledPath : innerPath;
|
|
437
|
+
const { path, clamped } = clampPath(requested, levels.length);
|
|
438
|
+
const pathKey = JSON.stringify(requested);
|
|
439
|
+
useEffect3(() => {
|
|
440
|
+
if (clamped) {
|
|
441
|
+
warn(
|
|
442
|
+
`path ${pathKey} is deeper than the ${levels.length} level(s) allow; it was truncated to ${JSON.stringify(path)}.`
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
}, [clamped, pathKey]);
|
|
446
|
+
const latest = useRef3({ path, levels: levels.length, controlled, onPathChange });
|
|
447
|
+
useEffect3(() => {
|
|
448
|
+
latest.current = { path, levels: levels.length, controlled, onPathChange };
|
|
449
|
+
});
|
|
450
|
+
const commit = useCallback((next) => {
|
|
451
|
+
const current = latest.current;
|
|
452
|
+
const { path: valid, clamped: tooDeep } = clampPath(next, current.levels);
|
|
453
|
+
if (tooDeep) {
|
|
454
|
+
warn(
|
|
455
|
+
`cannot drill to ${JSON.stringify(next)}: only ${current.levels} level(s) are defined (max depth ${current.levels - 1}).`
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
if (samePath(valid, current.path)) return valid;
|
|
459
|
+
latest.current = { ...current, path: valid };
|
|
460
|
+
if (!current.controlled) setInnerPath(valid);
|
|
461
|
+
current.onPathChange?.(valid);
|
|
462
|
+
return valid;
|
|
463
|
+
}, []);
|
|
464
|
+
const drillDown = useCallback(
|
|
465
|
+
(id) => commit([...latest.current.path, id]),
|
|
466
|
+
[commit]
|
|
467
|
+
);
|
|
468
|
+
const drillUp = useCallback(
|
|
469
|
+
(steps = 1) => {
|
|
470
|
+
const up = Number.isFinite(steps) ? Math.max(0, Math.floor(steps)) : 0;
|
|
471
|
+
const { path: current } = latest.current;
|
|
472
|
+
return commit(current.slice(0, Math.max(0, current.length - up)));
|
|
473
|
+
},
|
|
474
|
+
[commit]
|
|
475
|
+
);
|
|
476
|
+
const drillTo = commit;
|
|
477
|
+
const reset = useCallback(() => commit([]), [commit]);
|
|
478
|
+
const status = useSyncExternalStore2(bridge.subscribe, bridge.getStatus, bridge.getStatus);
|
|
479
|
+
const rootLabel = levels[0]?.label ?? levels[0]?.id ?? "";
|
|
480
|
+
const breadcrumbs = useMemo2(
|
|
481
|
+
() => buildBreadcrumbs({
|
|
482
|
+
path,
|
|
483
|
+
rootLabel,
|
|
484
|
+
getLabel: getLabel === void 0 ? void 0 : ({ id, depth }) => getLabel({ id, depth })
|
|
485
|
+
}),
|
|
486
|
+
// `getLabel` is usually an inline function: its identity must not recompute the breadcrumbs on every render.
|
|
487
|
+
// The route (`pathKey`) and the root do.
|
|
488
|
+
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
|
489
|
+
[pathKey, rootLabel]
|
|
490
|
+
);
|
|
491
|
+
const level = levels[path.length] ?? levels[levels.length - 1];
|
|
492
|
+
const context = useMemo2(() => contextOf(path), [path]);
|
|
493
|
+
return useMemo2(
|
|
494
|
+
() => ({
|
|
495
|
+
levels,
|
|
496
|
+
path,
|
|
497
|
+
context,
|
|
498
|
+
level,
|
|
499
|
+
breadcrumbs,
|
|
500
|
+
status,
|
|
501
|
+
canDrillUp: path.length > 0,
|
|
502
|
+
drillDown,
|
|
503
|
+
drillUp,
|
|
504
|
+
drillTo,
|
|
505
|
+
reset,
|
|
506
|
+
[DRILL_BRIDGE]: bridge
|
|
507
|
+
}),
|
|
508
|
+
[levels, path, context, level, breadcrumbs, status, drillDown, drillUp, drillTo, reset, bridge]
|
|
509
|
+
);
|
|
510
|
+
};
|
|
511
|
+
var isDrillController = (drill) => !Array.isArray(drill);
|
|
512
|
+
|
|
513
|
+
// src/react/use-drill-engine.ts
|
|
514
|
+
var REJECTED_AFTER_MS = 1e3;
|
|
515
|
+
var defaultFeatureId = (feature) => feature.id === void 0 ? void 0 : String(feature.id);
|
|
516
|
+
var collectionIds = /* @__PURE__ */ new WeakMap();
|
|
517
|
+
var nextCollectionId = 1;
|
|
518
|
+
var idOfCollection = (collection) => {
|
|
519
|
+
if (collection === void 0) return "-";
|
|
520
|
+
let id = collectionIds.get(collection);
|
|
521
|
+
if (id === void 0) {
|
|
522
|
+
id = nextCollectionId++;
|
|
523
|
+
collectionIds.set(collection, id);
|
|
524
|
+
}
|
|
525
|
+
return String(id);
|
|
526
|
+
};
|
|
527
|
+
var samePath2 = (a, b) => a.length === b.length && a.every((id, i) => id === b[i]);
|
|
528
|
+
var collections = /* @__PURE__ */ new WeakMap();
|
|
529
|
+
var filtered = /* @__PURE__ */ new WeakMap();
|
|
530
|
+
var featureCollectionOf = (geography, objectName) => {
|
|
531
|
+
const byName = collections.get(geography) ?? /* @__PURE__ */ new Map();
|
|
532
|
+
collections.set(geography, byName);
|
|
533
|
+
const name = objectName ?? "";
|
|
534
|
+
const found = byName.get(name);
|
|
535
|
+
if (found !== void 0) return found;
|
|
536
|
+
const created = toFeatureCollection({ geography, objectName });
|
|
537
|
+
byName.set(name, created);
|
|
538
|
+
return created;
|
|
539
|
+
};
|
|
540
|
+
var stableFilter = (raw, keep) => {
|
|
541
|
+
const kept = raw.features.flatMap((feature, index) => keep(feature) ? [index] : []);
|
|
542
|
+
if (kept.length === raw.features.length) return raw;
|
|
543
|
+
const signature = kept.join(",");
|
|
544
|
+
const bySignature = filtered.get(raw) ?? /* @__PURE__ */ new Map();
|
|
545
|
+
filtered.set(raw, bySignature);
|
|
546
|
+
const found = bySignature.get(signature);
|
|
547
|
+
if (found !== void 0) return found;
|
|
548
|
+
const created = {
|
|
549
|
+
...raw,
|
|
550
|
+
features: kept.flatMap((index) => {
|
|
551
|
+
const feature = raw.features[index];
|
|
552
|
+
return feature === void 0 ? [] : [feature];
|
|
553
|
+
})
|
|
554
|
+
};
|
|
555
|
+
bySignature.set(signature, created);
|
|
556
|
+
return created;
|
|
557
|
+
};
|
|
558
|
+
var isSync = (input) => typeof input !== "function" && !("then" in input);
|
|
559
|
+
var resolveInput = (input) => isSync(input) ? Promise.resolve(input) : (typeof input === "function" ? input() : input).then(unwrapGeography);
|
|
560
|
+
var fixedGeography = (level) => {
|
|
561
|
+
const { geography } = level;
|
|
562
|
+
return typeof geography === "function" || geography === "inherit" || !isSync(geography) ? void 0 : geography;
|
|
563
|
+
};
|
|
564
|
+
var NO_LEVELS = [
|
|
565
|
+
{ id: "root", geography: { type: "FeatureCollection", features: [] } }
|
|
566
|
+
];
|
|
567
|
+
var EMPTY_PATH = [];
|
|
568
|
+
var EMPTY_COLLECTION = { type: "FeatureCollection", features: [] };
|
|
569
|
+
var useDrillEngine = ({
|
|
570
|
+
drill,
|
|
571
|
+
data,
|
|
572
|
+
filterFeatures,
|
|
573
|
+
region,
|
|
574
|
+
store,
|
|
575
|
+
onDrill,
|
|
576
|
+
onDrillEnd,
|
|
577
|
+
onDrillError
|
|
578
|
+
}) => {
|
|
579
|
+
const active = drill !== void 0;
|
|
580
|
+
const internal = useDrillDown({
|
|
581
|
+
levels: drill === void 0 || isDrillController(drill) ? NO_LEVELS : drill
|
|
582
|
+
});
|
|
583
|
+
const controller = drill === void 0 ? null : isDrillController(drill) ? drill : internal;
|
|
584
|
+
const levels = controller?.levels ?? NO_LEVELS;
|
|
585
|
+
const target = controller?.path ?? EMPTY_PATH;
|
|
586
|
+
const targetKey = JSON.stringify(target);
|
|
587
|
+
const [machine, dispatch] = useReducer(drillReducer, target, createDrillState);
|
|
588
|
+
const [cache2] = useState3(() => createLruCache());
|
|
589
|
+
const [version, setVersion] = useState3(0);
|
|
590
|
+
const [failed, setFailed] = useState3(/* @__PURE__ */ new Map());
|
|
591
|
+
const pending = useRef4(/* @__PURE__ */ new Set());
|
|
592
|
+
const sourceOf = useCallback2((depth2) => sourceDepth(levels, depth2), [levels]);
|
|
593
|
+
const keyAt = (depth2, path) => geographyKeyFor(levels, path.slice(0, sourceOf(depth2)));
|
|
594
|
+
const geographyAt = (depth2, path) => {
|
|
595
|
+
const level2 = levels[sourceOf(depth2)];
|
|
596
|
+
if (level2 === void 0) return void 0;
|
|
597
|
+
return fixedGeography(level2) ?? cache2.get(keyAt(depth2, path));
|
|
598
|
+
};
|
|
599
|
+
const mounted = useRef4(false);
|
|
600
|
+
useEffect4(() => {
|
|
601
|
+
mounted.current = true;
|
|
602
|
+
return () => {
|
|
603
|
+
mounted.current = false;
|
|
604
|
+
};
|
|
605
|
+
}, []);
|
|
606
|
+
const request = (path, prefetching, retryFailed = false) => {
|
|
607
|
+
for (let depth2 = 0; depth2 <= path.length; depth2++) {
|
|
608
|
+
const source = sourceOf(depth2);
|
|
609
|
+
const level2 = levels[source];
|
|
610
|
+
if (level2 === void 0 || fixedGeography(level2) !== void 0) continue;
|
|
611
|
+
const key = keyAt(depth2, path);
|
|
612
|
+
if (cache2.has(key) || pending.current.has(key)) continue;
|
|
613
|
+
if (failed.has(key) && !retryFailed) continue;
|
|
614
|
+
const input = typeof level2.geography === "function" ? level2.geography(contextOf(path.slice(0, source))) : level2.geography;
|
|
615
|
+
if (input === "inherit") continue;
|
|
616
|
+
pending.current.add(key);
|
|
617
|
+
resolveInput(input).then(
|
|
618
|
+
(loaded) => {
|
|
619
|
+
pending.current.delete(key);
|
|
620
|
+
cache2.set(key, loaded);
|
|
621
|
+
if (mounted.current) setVersion((v) => v + 1);
|
|
622
|
+
},
|
|
623
|
+
(error) => {
|
|
624
|
+
pending.current.delete(key);
|
|
625
|
+
if (!mounted.current) return;
|
|
626
|
+
if (prefetching) setVersion((v) => v + 1);
|
|
627
|
+
else setFailed((previous2) => new Map(previous2).set(key, error));
|
|
628
|
+
}
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
const latestRequest = useRef4(request);
|
|
633
|
+
useEffect4(() => {
|
|
634
|
+
latestRequest.current = request;
|
|
635
|
+
});
|
|
636
|
+
const prefetch = useCallback2(
|
|
637
|
+
(path) => {
|
|
638
|
+
if (active) latestRequest.current(path, true);
|
|
639
|
+
},
|
|
640
|
+
[active]
|
|
641
|
+
);
|
|
642
|
+
const requestedTarget = useRef4("");
|
|
643
|
+
useEffect4(() => {
|
|
644
|
+
const retryFailed = requestedTarget.current !== targetKey;
|
|
645
|
+
requestedTarget.current = targetKey;
|
|
646
|
+
if (active) request(target, false, retryFailed);
|
|
647
|
+
}, [active, targetKey, levels, version]);
|
|
648
|
+
const keepFor = (depth2, path) => {
|
|
649
|
+
const source = sourceOf(depth2);
|
|
650
|
+
return (feature) => {
|
|
651
|
+
for (let k = source; k <= depth2; k++) {
|
|
652
|
+
const level2 = levels[k];
|
|
653
|
+
if (level2?.filterFeatures?.(feature, contextOf(path.slice(0, k))) === false) return false;
|
|
654
|
+
}
|
|
655
|
+
if (source !== 0) return true;
|
|
656
|
+
if (region !== void 0) {
|
|
657
|
+
const id = (levels[0]?.getFeatureId ?? defaultFeatureId)(feature);
|
|
658
|
+
if (id === void 0 || !region.ids.has(id)) return false;
|
|
659
|
+
}
|
|
660
|
+
return filterFeatures === void 0 || filterFeatures(feature);
|
|
661
|
+
};
|
|
662
|
+
};
|
|
663
|
+
const featuresAt = (depth2, path) => {
|
|
664
|
+
const geography2 = geographyAt(depth2, path);
|
|
665
|
+
if (geography2 === void 0) return void 0;
|
|
666
|
+
const source = sourceOf(depth2);
|
|
667
|
+
const raw = featureCollectionOf(geography2, levels[source]?.objectName);
|
|
668
|
+
const kept = stableFilter(raw, keepFor(depth2, path));
|
|
669
|
+
return source === 0 && region !== void 0 ? withoutOverseas(kept, region) : kept;
|
|
670
|
+
};
|
|
671
|
+
const rootGeography = active ? geographyAt(0, EMPTY_PATH) : void 0;
|
|
672
|
+
const rootAll = rootGeography === void 0 || region === void 0 ? void 0 : featureCollectionOf(rootGeography, levels[sourceOf(0)]?.objectName);
|
|
673
|
+
const featuresFor = (path) => Array.from({ length: path.length + 1 }, (_, d) => featuresAt(d, path));
|
|
674
|
+
const chainLevelOf = (depth2, fc) => ({
|
|
675
|
+
features: fc.features,
|
|
676
|
+
idOf: levels[depth2]?.getFeatureId ?? levels[sourceOf(depth2)]?.getFeatureId ?? defaultFeatureId
|
|
677
|
+
});
|
|
678
|
+
const chainLevelsFor = (path) => {
|
|
679
|
+
const collectionsByDepth = featuresFor(path);
|
|
680
|
+
const parents = collectionsByDepth.slice(0, path.length);
|
|
681
|
+
if (parents.some((fc) => fc === void 0)) return null;
|
|
682
|
+
return collectionsByDepth.map((fc, depth2) => chainLevelOf(depth2, fc ?? EMPTY_COLLECTION));
|
|
683
|
+
};
|
|
684
|
+
const targetFeatures = active ? featuresFor(target) : [];
|
|
685
|
+
const targetGeometry = active && targetFeatures.every((f) => f !== void 0);
|
|
686
|
+
const targetEmpty = targetGeometry && (targetFeatures[target.length]?.features.length ?? 0) === 0;
|
|
687
|
+
const liveData = resolveDrillData(data, contextOf(target));
|
|
688
|
+
const targetReady = targetGeometry && !targetEmpty && liveData !== void 0;
|
|
689
|
+
const shownPath = machine.phase === "revealing" ? machine.path : machine.settledPath;
|
|
690
|
+
const shownKey = JSON.stringify(shownPath);
|
|
691
|
+
const shownIsTarget = samePath2(shownPath, target);
|
|
692
|
+
const shownFeatures = active ? shownIsTarget ? targetFeatures : featuresFor(shownPath) : [];
|
|
693
|
+
const hasShown = active && shownFeatures.every((f) => f !== void 0);
|
|
694
|
+
const [latest] = useState3(() => ({
|
|
695
|
+
data: void 0,
|
|
696
|
+
path: shownPath,
|
|
697
|
+
chain: [],
|
|
698
|
+
fills: /* @__PURE__ */ new Map()
|
|
699
|
+
}));
|
|
700
|
+
const rememberFills = useCallback2(
|
|
701
|
+
(fills) => {
|
|
702
|
+
latest.fills = fills;
|
|
703
|
+
},
|
|
704
|
+
[latest]
|
|
705
|
+
);
|
|
706
|
+
const chainSig = `${shownKey}|${shownFeatures.map(idOfCollection).join(",")}`;
|
|
707
|
+
const chain = useMemo3(
|
|
708
|
+
() => shownFeatures.flatMap((fc, depth2) => fc === void 0 ? [] : [chainLevelOf(depth2, fc)]),
|
|
709
|
+
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
|
710
|
+
[chainSig, levels]
|
|
711
|
+
);
|
|
712
|
+
const [seen, setSeen] = useState3(shownPath);
|
|
713
|
+
const [previous, setPrevious] = useState3(null);
|
|
714
|
+
if (!samePath2(seen, shownPath)) {
|
|
715
|
+
setSeen(shownPath);
|
|
716
|
+
setPrevious({ path: latest.path, chain: latest.chain, fills: latest.fills });
|
|
717
|
+
}
|
|
718
|
+
useEffect4(() => {
|
|
719
|
+
latest.path = shownPath;
|
|
720
|
+
latest.chain = chain;
|
|
721
|
+
if (shownIsTarget && liveData !== void 0) latest.data = liveData;
|
|
722
|
+
});
|
|
723
|
+
const depth = shownPath.length;
|
|
724
|
+
const level = active ? levels[depth] ?? null : null;
|
|
725
|
+
const sourceLevel = levels[sourceOf(depth)];
|
|
726
|
+
const features = hasShown ? shownFeatures[depth] : void 0;
|
|
727
|
+
const geography = hasShown ? geographyAt(depth, shownPath) : void 0;
|
|
728
|
+
const emptyError = useMemo3(() => {
|
|
729
|
+
if (!targetEmpty) return null;
|
|
730
|
+
const parent = target.at(-1) ?? null;
|
|
731
|
+
const id = levels[target.length]?.id ?? "?";
|
|
732
|
+
return new Error(
|
|
733
|
+
`[choropleth] drill level "${id}" has 0 features for "${parent}". Hint: return false from canDrill for it.`
|
|
734
|
+
);
|
|
735
|
+
}, [targetEmpty, targetKey, levels]);
|
|
736
|
+
const failure = (() => {
|
|
737
|
+
if (!active) return null;
|
|
738
|
+
for (let d = 0; d <= target.length; d++) {
|
|
739
|
+
const error = failed.get(keyAt(d, target));
|
|
740
|
+
if (error !== void 0) return { error };
|
|
741
|
+
}
|
|
742
|
+
return emptyError === null ? null : { error: emptyError };
|
|
743
|
+
})();
|
|
744
|
+
const latestProps = useRef4({ onDrill, onDrillEnd, onDrillError, controller, target, machine });
|
|
745
|
+
useEffect4(() => {
|
|
746
|
+
latestProps.current = { onDrill, onDrillEnd, onDrillError, controller, target, machine };
|
|
747
|
+
});
|
|
748
|
+
const waiters = useRef4([]);
|
|
749
|
+
const finish = (waiter, completed) => {
|
|
750
|
+
clearTimeout(waiter.timer);
|
|
751
|
+
waiters.current = waiters.current.filter((w) => w !== waiter);
|
|
752
|
+
waiter.resolve({ completed });
|
|
753
|
+
};
|
|
754
|
+
const arrived = (waiter) => {
|
|
755
|
+
const { target: wanted, machine: current } = latestProps.current;
|
|
756
|
+
if (!samePath2(wanted, waiter.target)) return false;
|
|
757
|
+
waiter.seen = true;
|
|
758
|
+
return current.phase === "idle" && samePath2(current.settledPath, waiter.target);
|
|
759
|
+
};
|
|
760
|
+
const navigate = useCallback2((run) => {
|
|
761
|
+
const ctl = latestProps.current.controller;
|
|
762
|
+
if (ctl === null) return Promise.resolve({ completed: false });
|
|
763
|
+
const wanted = run(ctl);
|
|
764
|
+
return new Promise((resolve) => {
|
|
765
|
+
const waiter = { target: wanted, resolve, seen: false, timer: void 0 };
|
|
766
|
+
waiter.timer = setTimeout(() => {
|
|
767
|
+
if (!waiter.seen) finish(waiter, false);
|
|
768
|
+
}, REJECTED_AFTER_MS);
|
|
769
|
+
for (const other of waiters.current) {
|
|
770
|
+
if (!samePath2(other.target, wanted)) finish(other, false);
|
|
771
|
+
}
|
|
772
|
+
waiters.current.push(waiter);
|
|
773
|
+
if (arrived(waiter)) finish(waiter, true);
|
|
774
|
+
});
|
|
775
|
+
}, []);
|
|
776
|
+
useEffect4(() => {
|
|
777
|
+
for (const waiter of waiters.current) {
|
|
778
|
+
if (arrived(waiter)) finish(waiter, true);
|
|
779
|
+
else if (waiter.seen && !samePath2(target, waiter.target)) finish(waiter, false);
|
|
780
|
+
}
|
|
781
|
+
});
|
|
782
|
+
useEffect4(
|
|
783
|
+
() => () => {
|
|
784
|
+
for (const waiter of waiters.current) finish(waiter, false);
|
|
785
|
+
},
|
|
786
|
+
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
|
787
|
+
[]
|
|
788
|
+
);
|
|
789
|
+
const dispatched = useRef4(target);
|
|
790
|
+
const machineAtDispatch = useRef4(machine);
|
|
791
|
+
const clicked = useRef4(void 0);
|
|
792
|
+
const noteItem = useCallback2((item) => {
|
|
793
|
+
clicked.current = item;
|
|
794
|
+
}, []);
|
|
795
|
+
const settledKey = JSON.stringify(machine.settledPath);
|
|
796
|
+
useEffect4(() => {
|
|
797
|
+
if (!active || controller === null) return;
|
|
798
|
+
if (samePath2(dispatched.current, target)) return;
|
|
799
|
+
const direction = classifyDrill(machine.settledPath, target);
|
|
800
|
+
const eager = direction === "down" || direction === "lateral" || direction === "equal";
|
|
801
|
+
if (!eager && !targetReady) return;
|
|
802
|
+
dispatched.current = target;
|
|
803
|
+
machineAtDispatch.current = machine;
|
|
804
|
+
const view = store.getZoom()?.getView();
|
|
805
|
+
dispatch({
|
|
806
|
+
type: "DRILL",
|
|
807
|
+
to: target,
|
|
808
|
+
cached: targetReady,
|
|
809
|
+
...view === void 0 ? {} : { camera: { k: view.k, x: view.x, y: view.y } }
|
|
810
|
+
});
|
|
811
|
+
}, [active, targetKey, targetReady, settledKey]);
|
|
812
|
+
useEffect4(() => {
|
|
813
|
+
if (!active || machine.geometry !== "pending" || machine.geometryKey === null) return;
|
|
814
|
+
if (targetReady && samePath2(machine.path, target)) {
|
|
815
|
+
dispatch({ type: "GEOMETRY_READY", key: machine.geometryKey });
|
|
816
|
+
}
|
|
817
|
+
}, [active, targetReady, machine.geometry, machine.geometryKey]);
|
|
818
|
+
const reported = useRef4(null);
|
|
819
|
+
useEffect4(() => {
|
|
820
|
+
if (!active || failure === null || reported.current === failure.error) return;
|
|
821
|
+
reported.current = failure.error;
|
|
822
|
+
const { onDrillError: report, controller: ctl, machine: current } = latestProps.current;
|
|
823
|
+
const wanted = latestProps.current.target;
|
|
824
|
+
if (report !== void 0) report({ error: failure.error, ctx: contextOf(wanted) });
|
|
825
|
+
else if (!isProduction()) {
|
|
826
|
+
const reason = failure.error instanceof Error ? failure.error.message : String(failure.error);
|
|
827
|
+
console.error(
|
|
828
|
+
reason.startsWith("[choropleth]") ? reason : `[choropleth] drill: failed to load the geometry for ${JSON.stringify(wanted)}: ${reason}`
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
if (samePath2(dispatched.current, wanted) && machine.geometryKey !== null) {
|
|
832
|
+
dispatch({ type: "GEOMETRY_ERROR", key: machine.geometryKey });
|
|
833
|
+
} else if (ctl !== null) {
|
|
834
|
+
dispatched.current = current.settledPath;
|
|
835
|
+
ctl.drillTo(current.settledPath);
|
|
836
|
+
}
|
|
837
|
+
}, [active, failure]);
|
|
838
|
+
const machinePathKey = JSON.stringify(machine.path);
|
|
839
|
+
useEffect4(() => {
|
|
840
|
+
if (!active || controller === null) return;
|
|
841
|
+
if (machine === machineAtDispatch.current) return;
|
|
842
|
+
if (!samePath2(dispatched.current, target) || samePath2(machine.path, target)) return;
|
|
843
|
+
dispatched.current = machine.path;
|
|
844
|
+
machineAtDispatch.current = machine;
|
|
845
|
+
controller.drillTo(machine.path);
|
|
846
|
+
}, [active, machinePathKey, targetKey]);
|
|
847
|
+
const lastTarget = useRef4(target);
|
|
848
|
+
useEffect4(() => {
|
|
849
|
+
if (!active || samePath2(lastTarget.current, target)) return;
|
|
850
|
+
const event = buildDrillEvent({
|
|
851
|
+
from: lastTarget.current,
|
|
852
|
+
to: target,
|
|
853
|
+
item: clicked.current
|
|
854
|
+
});
|
|
855
|
+
lastTarget.current = target;
|
|
856
|
+
clicked.current = void 0;
|
|
857
|
+
if (event !== null) latestProps.current.onDrill?.(event);
|
|
858
|
+
}, [active, targetKey]);
|
|
859
|
+
const lastEnd = useRef4(machine.settledPath);
|
|
860
|
+
useEffect4(() => {
|
|
861
|
+
if (!active || machine.phase !== "idle") return;
|
|
862
|
+
const previousEnd = lastEnd.current;
|
|
863
|
+
if (samePath2(previousEnd, machine.settledPath)) return;
|
|
864
|
+
lastEnd.current = machine.settledPath;
|
|
865
|
+
const event = buildDrillEvent({ from: previousEnd, to: machine.settledPath });
|
|
866
|
+
if (event !== null) latestProps.current.onDrillEnd?.(event);
|
|
867
|
+
}, [active, machine.phase, settledKey]);
|
|
868
|
+
const status = !active ? "loading" : hasShown ? "ready" : failure !== null ? "error" : "loading";
|
|
869
|
+
const bridge = controller?.[DRILL_BRIDGE];
|
|
870
|
+
const phase = machine.phase;
|
|
871
|
+
useEffect4(() => {
|
|
872
|
+
bridge?.setStatus(phase);
|
|
873
|
+
}, [bridge, phase]);
|
|
874
|
+
return {
|
|
875
|
+
active,
|
|
876
|
+
controller,
|
|
877
|
+
levels,
|
|
878
|
+
shownPath,
|
|
879
|
+
shownKey,
|
|
880
|
+
depth,
|
|
881
|
+
level,
|
|
882
|
+
geography,
|
|
883
|
+
status,
|
|
884
|
+
objectName: sourceLevel?.objectName,
|
|
885
|
+
features,
|
|
886
|
+
levelFeatures: shownFeatures,
|
|
887
|
+
getFeatureId: level?.getFeatureId ?? sourceLevel?.getFeatureId,
|
|
888
|
+
chain,
|
|
889
|
+
chainSig,
|
|
890
|
+
filterFeatures: active && hasShown ? keepFor(depth, shownPath) : void 0,
|
|
891
|
+
rootFeatures: hasShown ? shownFeatures[0] : void 0,
|
|
892
|
+
rootAll,
|
|
893
|
+
data: !active ? void 0 : shownIsTarget ? liveData : latest.data,
|
|
894
|
+
noteItem,
|
|
895
|
+
machine,
|
|
896
|
+
dispatch,
|
|
897
|
+
previous,
|
|
898
|
+
rememberFills,
|
|
899
|
+
chainLevelsFor,
|
|
900
|
+
target,
|
|
901
|
+
prefetch,
|
|
902
|
+
navigate
|
|
903
|
+
};
|
|
904
|
+
};
|
|
905
|
+
|
|
906
|
+
// src/react/use-geography.ts
|
|
907
|
+
import { useEffect as useEffect5, useState as useState4 } from "react";
|
|
908
|
+
var promisesByKey = /* @__PURE__ */ new Map();
|
|
909
|
+
var promisesByIdentity = /* @__PURE__ */ new WeakMap();
|
|
910
|
+
var valuesByKey = /* @__PURE__ */ new Map();
|
|
911
|
+
var valuesByIdentity = /* @__PURE__ */ new WeakMap();
|
|
912
|
+
var isDeferred = (input) => typeof input === "function" || "then" in input;
|
|
913
|
+
var peek = (input, key) => {
|
|
914
|
+
if (!isDeferred(input)) return input;
|
|
915
|
+
return key === void 0 ? valuesByIdentity.get(input) : valuesByKey.get(key);
|
|
916
|
+
};
|
|
917
|
+
var load = (input, key) => {
|
|
918
|
+
const cached = key === void 0 ? promisesByIdentity.get(input) : promisesByKey.get(key);
|
|
919
|
+
if (cached !== void 0) return cached;
|
|
920
|
+
const promise = (typeof input === "function" ? input() : input).then((module) => {
|
|
921
|
+
const value = unwrapGeography(module);
|
|
922
|
+
if (key === void 0) valuesByIdentity.set(input, value);
|
|
923
|
+
else valuesByKey.set(key, value);
|
|
924
|
+
return value;
|
|
925
|
+
});
|
|
926
|
+
if (key === void 0) promisesByIdentity.set(input, promise);
|
|
927
|
+
else promisesByKey.set(key, promise);
|
|
928
|
+
promise.catch(() => {
|
|
929
|
+
if (key === void 0) promisesByIdentity.delete(input);
|
|
930
|
+
else promisesByKey.delete(key);
|
|
931
|
+
});
|
|
932
|
+
return promise;
|
|
933
|
+
};
|
|
934
|
+
var useGeography = (input, key, onError) => {
|
|
935
|
+
const ready = peek(input, key);
|
|
936
|
+
const [loaded, setLoaded] = useState4(void 0);
|
|
937
|
+
const [failed, setFailed] = useState4(false);
|
|
938
|
+
useEffect5(() => {
|
|
939
|
+
if (ready !== void 0 || !isDeferred(input)) return;
|
|
940
|
+
let cancelled = false;
|
|
941
|
+
load(input, key).then(
|
|
942
|
+
(value) => {
|
|
943
|
+
if (cancelled) return;
|
|
944
|
+
setFailed(false);
|
|
945
|
+
setLoaded(value);
|
|
946
|
+
},
|
|
947
|
+
(reason) => {
|
|
948
|
+
if (cancelled) return;
|
|
949
|
+
const error = reason instanceof Error ? reason : new Error(String(reason));
|
|
950
|
+
setFailed(true);
|
|
951
|
+
if (onError === void 0)
|
|
952
|
+
console.error(`[choropleth] Failed to load geography: ${error.message}`);
|
|
953
|
+
else onError(error);
|
|
954
|
+
}
|
|
955
|
+
);
|
|
956
|
+
return () => {
|
|
957
|
+
cancelled = true;
|
|
958
|
+
};
|
|
959
|
+
}, [input, key, ready]);
|
|
960
|
+
const geography = ready ?? loaded;
|
|
961
|
+
if (geography !== void 0) return { geography, status: "ready" };
|
|
962
|
+
return { geography: void 0, status: failed ? "error" : "loading" };
|
|
963
|
+
};
|
|
964
|
+
var clearGeographyCache = () => {
|
|
965
|
+
promisesByKey.clear();
|
|
966
|
+
valuesByKey.clear();
|
|
967
|
+
};
|
|
968
|
+
|
|
969
|
+
// src/react/use-choropleth.ts
|
|
970
|
+
var FALLBACK_POINT_RADIUS = 4;
|
|
971
|
+
var useChoropleth = (rawOptions) => {
|
|
972
|
+
const [store] = useState5(createMapStore);
|
|
973
|
+
const region = useRegion(rawOptions.region);
|
|
974
|
+
const engine = useDrillEngine({
|
|
975
|
+
drill: rawOptions.drill,
|
|
976
|
+
data: rawOptions.data,
|
|
977
|
+
filterFeatures: rawOptions.filterFeatures,
|
|
978
|
+
region,
|
|
979
|
+
store,
|
|
980
|
+
onDrill: rawOptions.onDrill,
|
|
981
|
+
onDrillEnd: rawOptions.onDrillEnd,
|
|
982
|
+
onDrillError: rawOptions.onDrillError
|
|
983
|
+
});
|
|
984
|
+
const encoding = engine.level?.encoding;
|
|
985
|
+
const options = engine.active ? {
|
|
986
|
+
...rawOptions,
|
|
987
|
+
...encoding,
|
|
988
|
+
getId: encoding?.getId ?? rawOptions.getId,
|
|
989
|
+
data: engine.data,
|
|
990
|
+
getFeatureId: engine.getFeatureId ?? rawOptions.getFeatureId
|
|
991
|
+
} : { ...rawOptions, data: resolveDrillData(rawOptions.data, contextOf([])) };
|
|
992
|
+
const { getId, getFeatureId, projection, fallbackPoints } = options;
|
|
993
|
+
const isBubble = options.variant === "bubble";
|
|
994
|
+
const labels = resolveLabels(options.labels);
|
|
995
|
+
const single = useGeography(
|
|
996
|
+
engine.active ? NO_GEOGRAPHY : options.geography ?? NO_GEOGRAPHY,
|
|
997
|
+
options.geographyKey,
|
|
998
|
+
options.onGeographyError
|
|
999
|
+
);
|
|
1000
|
+
if (!engine.active && options.geography === void 0) {
|
|
1001
|
+
throw new Error(
|
|
1002
|
+
"[choropleth] Missing `geography`: pass `geography` (TopoJSON/GeoJSON) or `drill` (a list of levels)."
|
|
1003
|
+
);
|
|
1004
|
+
}
|
|
1005
|
+
const geography = engine.active ? engine.geography : single.geography;
|
|
1006
|
+
const status = engine.active ? engine.status : single.status;
|
|
1007
|
+
const objectName = engine.active ? engine.objectName : options.objectName;
|
|
1008
|
+
const regionIdOf = getFeatureId ?? defaultFeatureId2;
|
|
1009
|
+
const userFilter = engine.active ? engine.filterFeatures : options.filterFeatures;
|
|
1010
|
+
const filterFeatures = engine.active || region === void 0 ? userFilter : (feature) => {
|
|
1011
|
+
const id = regionIdOf(feature);
|
|
1012
|
+
return id !== void 0 && region.ids.has(id) && (userFilter?.(feature) ?? true);
|
|
1013
|
+
};
|
|
1014
|
+
const allFeatures = useMemo4(
|
|
1015
|
+
() => engine.active ? engine.features ?? EMPTY_FEATURES : geography === void 0 ? EMPTY_FEATURES : toFeatureCollection({ geography, objectName }),
|
|
1016
|
+
// `engine.features` already comes filtered and is stable (the engine's caches).
|
|
1017
|
+
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
|
1018
|
+
[engine.active, engine.features, geography, objectName]
|
|
1019
|
+
);
|
|
1020
|
+
const keptIndexes = filterFeatures === void 0 ? null : allFeatures.features.flatMap((f, index) => filterFeatures(f) ? [index] : []);
|
|
1021
|
+
const keptKey = keptIndexes === null ? "" : keptIndexes.join(",");
|
|
1022
|
+
const features = useMemo4(() => {
|
|
1023
|
+
const kept = keptIndexes === null ? allFeatures : {
|
|
1024
|
+
...allFeatures,
|
|
1025
|
+
features: keptIndexes.flatMap((index) => {
|
|
1026
|
+
const feature = allFeatures.features[index];
|
|
1027
|
+
return feature === void 0 ? [] : [feature];
|
|
1028
|
+
})
|
|
1029
|
+
};
|
|
1030
|
+
return engine.active || region === void 0 ? kept : withoutOverseas(kept, region);
|
|
1031
|
+
}, [allFeatures, keptKey, engine.active, region?.key]);
|
|
1032
|
+
const bordersGeometry = useMemo4(
|
|
1033
|
+
() => geography === void 0 ? null : createBorders({ geography, objectName, filterFeatures }),
|
|
1034
|
+
// `filterFeatures` is summarized in `keptKey`: its identity must not relaunch the computation.
|
|
1035
|
+
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
|
1036
|
+
[geography, objectName, keptKey]
|
|
1037
|
+
);
|
|
1038
|
+
const rotationFeatures = engine.active ? engine.rootFeatures : features;
|
|
1039
|
+
const rotation = useMemo4(() => {
|
|
1040
|
+
if (region === void 0 || rotationFeatures === void 0) return void 0;
|
|
1041
|
+
const points = Object.entries(fallbackPoints ?? {}).flatMap(
|
|
1042
|
+
([id, point]) => region.ids.has(id) ? [point] : []
|
|
1043
|
+
);
|
|
1044
|
+
return regionRotation(rotationFeatures.features, points);
|
|
1045
|
+
}, [region?.key, rotationFeatures, fallbackPoints]);
|
|
1046
|
+
const rotateLon = rotation?.[0];
|
|
1047
|
+
const rotate = useMemo4(
|
|
1048
|
+
() => rotateLon === void 0 ? void 0 : [rotateLon, 0],
|
|
1049
|
+
[rotateLon]
|
|
1050
|
+
);
|
|
1051
|
+
const autoAspect = useMemo4(
|
|
1052
|
+
() => options.aspectRatio === "auto" ? computeAspectRatio({
|
|
1053
|
+
name: projection,
|
|
1054
|
+
// With drill the proportion is the root's: it must not change when going down a level.
|
|
1055
|
+
featureCollection: (engine.active ? engine.rootFeatures : void 0) ?? features,
|
|
1056
|
+
rotate
|
|
1057
|
+
}) : void 0,
|
|
1058
|
+
[options.aspectRatio, projection, features, engine.active, engine.rootFeatures, rotate]
|
|
1059
|
+
);
|
|
1060
|
+
const measured = useContainerSize({
|
|
1061
|
+
aspectRatio: options.aspectRatio === "auto" ? autoAspect : options.aspectRatio
|
|
1062
|
+
});
|
|
1063
|
+
const width = options.width ?? measured.width;
|
|
1064
|
+
const height = options.height ?? measured.height;
|
|
1065
|
+
const rebasePadding = typeof options.zoom === "object" ? options.zoom.padding ?? 24 : 24;
|
|
1066
|
+
const drillFocusBounds = engine.levels.reduce(
|
|
1067
|
+
(all, level) => ({ ...all, ...level.focusBounds }),
|
|
1068
|
+
{ ...DEFAULT_FOCUS_BOUNDS, ...options.focusBounds }
|
|
1069
|
+
);
|
|
1070
|
+
const focusKey = JSON.stringify(drillFocusBounds);
|
|
1071
|
+
const chain = useMemo4(
|
|
1072
|
+
() => engine.active && engine.chain.length > 0 && width > 0 && height > 0 ? buildProjectionChain({
|
|
1073
|
+
levels: engine.chain,
|
|
1074
|
+
path: engine.shownPath,
|
|
1075
|
+
name: projection,
|
|
1076
|
+
width,
|
|
1077
|
+
height,
|
|
1078
|
+
padding: rebasePadding,
|
|
1079
|
+
focusBounds: drillFocusBounds,
|
|
1080
|
+
rotate
|
|
1081
|
+
}) : null,
|
|
1082
|
+
// `chainSig` summarizes the content of `engine.chain`; `focusKey`, that of `drillFocusBounds`.
|
|
1083
|
+
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
|
1084
|
+
[engine.active, engine.chainSig, projection, width, height, rebasePadding, focusKey, rotate]
|
|
1085
|
+
);
|
|
1086
|
+
const chainMissing = chain?.missing ?? null;
|
|
1087
|
+
useEffect6(() => {
|
|
1088
|
+
if (chainMissing !== null && !isProduction()) {
|
|
1089
|
+
console.warn(
|
|
1090
|
+
`[choropleth] drill: could not frame region "${chainMissing}" (it is not in its parent level, or it has no extent). The map keeps the parent's framing.`
|
|
1091
|
+
);
|
|
1092
|
+
}
|
|
1093
|
+
}, [chainMissing]);
|
|
1094
|
+
const chainFor = (levels, path) => buildProjectionChain({
|
|
1095
|
+
levels,
|
|
1096
|
+
path,
|
|
1097
|
+
name: projection,
|
|
1098
|
+
width,
|
|
1099
|
+
height,
|
|
1100
|
+
padding: rebasePadding,
|
|
1101
|
+
focusBounds: drillFocusBounds,
|
|
1102
|
+
rotate
|
|
1103
|
+
});
|
|
1104
|
+
useDrillChoreography({ engine, store, shownChain: chain, chainFor });
|
|
1105
|
+
const { machine, previous } = engine;
|
|
1106
|
+
const leaving = machine.layers.some((layer) => layer.role === "exiting");
|
|
1107
|
+
const previousChain = useMemo4(
|
|
1108
|
+
() => engine.active && previous !== null && width > 0 && height > 0 && (leaving || machine.direction === "up") ? buildProjectionChain({
|
|
1109
|
+
levels: [...previous.chain],
|
|
1110
|
+
path: previous.path,
|
|
1111
|
+
name: projection,
|
|
1112
|
+
width,
|
|
1113
|
+
height,
|
|
1114
|
+
padding: rebasePadding,
|
|
1115
|
+
focusBounds: drillFocusBounds,
|
|
1116
|
+
rotate
|
|
1117
|
+
}) : null,
|
|
1118
|
+
// `previous` changes only when the level changes; `focusKey` summarizes `drillFocusBounds`.
|
|
1119
|
+
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
|
1120
|
+
[
|
|
1121
|
+
engine.active,
|
|
1122
|
+
previous,
|
|
1123
|
+
leaving,
|
|
1124
|
+
machine.direction,
|
|
1125
|
+
projection,
|
|
1126
|
+
width,
|
|
1127
|
+
height,
|
|
1128
|
+
rebasePadding,
|
|
1129
|
+
focusKey,
|
|
1130
|
+
rotate
|
|
1131
|
+
]
|
|
1132
|
+
);
|
|
1133
|
+
const activeProjection = chain?.projections[chain.projections.length - 1];
|
|
1134
|
+
const layersKey = machine.layers.map((layer) => `${layer.key}:${layer.role}`).join("|");
|
|
1135
|
+
const contextMode = engine.level?.context ?? "siblings";
|
|
1136
|
+
const drillLayers = useMemo4(() => {
|
|
1137
|
+
if (!engine.active || chain === null || activeProjection === void 0) return [];
|
|
1138
|
+
const views = [];
|
|
1139
|
+
const activeSource = sourceDepth(engine.levels, engine.depth);
|
|
1140
|
+
for (const layer of machine.layers) {
|
|
1141
|
+
let features2;
|
|
1142
|
+
let projectionOf;
|
|
1143
|
+
let idOf2;
|
|
1144
|
+
let focusId;
|
|
1145
|
+
if (layer.role === "context") {
|
|
1146
|
+
if (contextMode === "none" || layer.depth >= engine.depth) continue;
|
|
1147
|
+
if (sourceDepth(engine.levels, layer.depth) === activeSource) continue;
|
|
1148
|
+
features2 = engine.levelFeatures[layer.depth]?.features;
|
|
1149
|
+
projectionOf = chain.projections[layer.depth];
|
|
1150
|
+
idOf2 = engine.chain[layer.depth]?.idOf;
|
|
1151
|
+
focusId = engine.shownPath[layer.depth];
|
|
1152
|
+
} else if (layer.role === "exiting") {
|
|
1153
|
+
features2 = previous?.chain[layer.depth]?.features;
|
|
1154
|
+
projectionOf = previousChain?.projections[layer.depth];
|
|
1155
|
+
idOf2 = previous?.chain[layer.depth]?.idOf;
|
|
1156
|
+
} else {
|
|
1157
|
+
continue;
|
|
1158
|
+
}
|
|
1159
|
+
if (features2 === void 0 || projectionOf === void 0 || idOf2 === void 0) continue;
|
|
1160
|
+
const path = geoPath(projectionOf);
|
|
1161
|
+
const level = engine.levels[layer.depth];
|
|
1162
|
+
const paths2 = features2.flatMap((feature) => {
|
|
1163
|
+
const d = path(feature);
|
|
1164
|
+
if (d == null) return [];
|
|
1165
|
+
const id = idOf2(feature);
|
|
1166
|
+
const drillable = layer.role === "context" && id !== void 0 && isDrillable(engine.levels, layer.depth, {
|
|
1167
|
+
kind: "region",
|
|
1168
|
+
id,
|
|
1169
|
+
feature,
|
|
1170
|
+
datum: void 0,
|
|
1171
|
+
level: { id: level?.id ?? ROOT_LEVEL.id, depth: layer.depth },
|
|
1172
|
+
drillable: true
|
|
1173
|
+
});
|
|
1174
|
+
const fill = layer.role === "exiting" && id !== void 0 ? previous?.fills.get(id) : void 0;
|
|
1175
|
+
return [{ id, d, focus: id !== void 0 && id === focusId, drillable, fill }];
|
|
1176
|
+
});
|
|
1177
|
+
views.push({
|
|
1178
|
+
key: layer.key,
|
|
1179
|
+
role: layer.role,
|
|
1180
|
+
depth: layer.depth,
|
|
1181
|
+
transform: relativeTransform(projectionOf, activeProjection),
|
|
1182
|
+
paths: paths2
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
return views;
|
|
1186
|
+
}, [engine.active, chain, previousChain, previous, layersKey, engine.chainSig, contextMode]);
|
|
1187
|
+
const controller = engine.controller;
|
|
1188
|
+
const drillNav = useMemo4(() => {
|
|
1189
|
+
if (!engine.active || controller === null) return null;
|
|
1190
|
+
const rootLabel = engine.levels[0]?.label ?? labels.breadcrumbRoot;
|
|
1191
|
+
const items = controller.breadcrumbs.map((crumb) => {
|
|
1192
|
+
let label = crumb.label;
|
|
1193
|
+
if (crumb.id === null) label = rootLabel;
|
|
1194
|
+
else if (crumb.label === crumb.id) {
|
|
1195
|
+
const parent = engine.chain[crumb.depth - 1];
|
|
1196
|
+
const feature = parent?.features.find((f) => parent.idOf(f) === crumb.id);
|
|
1197
|
+
label = (feature === void 0 ? void 0 : nameOf(feature)) ?? crumb.id;
|
|
1198
|
+
}
|
|
1199
|
+
return {
|
|
1200
|
+
depth: crumb.depth,
|
|
1201
|
+
id: crumb.id,
|
|
1202
|
+
label,
|
|
1203
|
+
current: crumb.depth === controller.path.length
|
|
1204
|
+
};
|
|
1205
|
+
});
|
|
1206
|
+
return {
|
|
1207
|
+
items,
|
|
1208
|
+
canDrillUp: controller.canDrillUp,
|
|
1209
|
+
drillTo: (depth) => controller.drillTo(controller.path.slice(0, depth)),
|
|
1210
|
+
drillUp: (steps) => controller.drillUp(steps)
|
|
1211
|
+
};
|
|
1212
|
+
}, [
|
|
1213
|
+
engine.active,
|
|
1214
|
+
controller,
|
|
1215
|
+
controller?.path,
|
|
1216
|
+
engine.chainSig,
|
|
1217
|
+
engine.levels,
|
|
1218
|
+
labels.breadcrumbRoot
|
|
1219
|
+
]);
|
|
1220
|
+
const drillFocusId = engine.active && (machine.phase === "flying" || machine.phase === "loading") && machine.direction !== "up" && machine.direction !== null ? machine.path.at(-1) ?? null : null;
|
|
1221
|
+
const { paths, bordersPath, anchors, project, invert, toPath } = useMemo4(() => {
|
|
1222
|
+
const geoProjection = chain?.projections[chain.projections.length - 1] ?? createProjection({
|
|
1223
|
+
name: projection,
|
|
1224
|
+
width,
|
|
1225
|
+
height,
|
|
1226
|
+
featureCollection: features,
|
|
1227
|
+
rotate
|
|
1228
|
+
});
|
|
1229
|
+
const toPath2 = geoPath(geoProjection);
|
|
1230
|
+
const toPoint = (coordinates) => {
|
|
1231
|
+
const at = geoProjection([...coordinates]);
|
|
1232
|
+
return at !== null && Number.isFinite(at[0]) && Number.isFinite(at[1]) ? at : null;
|
|
1233
|
+
};
|
|
1234
|
+
return {
|
|
1235
|
+
paths: features.features.map((f) => toPath2(f)),
|
|
1236
|
+
bordersPath: bordersGeometry === null ? null : toPath2(bordersGeometry),
|
|
1237
|
+
// Anchors are only computed for `bubble` (the only thing that uses them). `geoPath`'s centroid counts
|
|
1238
|
+
// distant territories (France's bubble would land in the Atlantic): the anchor is the pole of
|
|
1239
|
+
// inaccessibility of the main body. The centroid remains as a fallback.
|
|
1240
|
+
anchors: isBubble ? features.features.map((f) => {
|
|
1241
|
+
const anchor = getAnchorPoint({
|
|
1242
|
+
geometry: getFocusGeometry({ feature: f, path: toPath2 }),
|
|
1243
|
+
project: toPoint
|
|
1244
|
+
});
|
|
1245
|
+
if (anchor !== null) return anchor;
|
|
1246
|
+
const [x, y] = toPath2.centroid(f);
|
|
1247
|
+
return Number.isFinite(x) && Number.isFinite(y) ? [x, y] : null;
|
|
1248
|
+
}) : [],
|
|
1249
|
+
project: toPoint,
|
|
1250
|
+
toPath: toPath2,
|
|
1251
|
+
// The reverse: pixels → [lon, lat]. Used by `toViewState` for the public `ViewState`.
|
|
1252
|
+
invert: (point) => {
|
|
1253
|
+
const at = geoProjection.invert?.([point[0], point[1]]);
|
|
1254
|
+
return at != null && Number.isFinite(at[0]) && Number.isFinite(at[1]) ? [at[0], at[1]] : null;
|
|
1255
|
+
}
|
|
1256
|
+
};
|
|
1257
|
+
}, [features, bordersGeometry, projection, width, height, isBubble, chain, rotate]);
|
|
1258
|
+
const lastData = useRef5(options.data);
|
|
1259
|
+
if (options.data !== void 0) lastData.current = options.data;
|
|
1260
|
+
const data = options.data ?? lastData.current ?? NO_DATA;
|
|
1261
|
+
const dataLoading = options.loading ?? options.data === void 0;
|
|
1262
|
+
const idOf = getFeatureId ?? defaultFeatureId2;
|
|
1263
|
+
const join = joinData({ features: features.features, data, getId, getFeatureId: idOf });
|
|
1264
|
+
const outOfRegion = [];
|
|
1265
|
+
let unmatchedIds = join.unmatchedData;
|
|
1266
|
+
const regionDrawn = !engine.active || sourceDepth(engine.levels, engine.depth) === 0;
|
|
1267
|
+
if (region !== void 0 && regionDrawn && unmatchedIds.length > 0) {
|
|
1268
|
+
const world = engine.active ? engine.rootAll : allFeatures;
|
|
1269
|
+
const worldIds = /* @__PURE__ */ new Set();
|
|
1270
|
+
for (const feature of world?.features ?? []) {
|
|
1271
|
+
const id = idOf(feature);
|
|
1272
|
+
if (id !== void 0) worldIds.add(id);
|
|
1273
|
+
}
|
|
1274
|
+
unmatchedIds = unmatchedIds.filter((id) => {
|
|
1275
|
+
const outside = !region.ids.has(id) && (worldIds.has(id) || fallbackPoints?.[id] !== void 0);
|
|
1276
|
+
if (outside) outOfRegion.push(id);
|
|
1277
|
+
return !outside;
|
|
1278
|
+
});
|
|
1279
|
+
}
|
|
1280
|
+
const fallbacks = [];
|
|
1281
|
+
if (fallbackPoints !== void 0 && unmatchedIds.length > 0) {
|
|
1282
|
+
const wanted = new Set(unmatchedIds.filter((id) => fallbackPoints[id] !== void 0));
|
|
1283
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1284
|
+
for (const datum of data) {
|
|
1285
|
+
const id = getId(datum);
|
|
1286
|
+
const coordinates = fallbackPoints[id];
|
|
1287
|
+
const at = coordinates === void 0 ? null : project(coordinates);
|
|
1288
|
+
if (wanted.has(id) && !seen.has(id) && coordinates !== void 0 && at !== null) {
|
|
1289
|
+
seen.add(id);
|
|
1290
|
+
fallbacks.push({ id, datum, coordinates, at });
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
const fallbackIds = new Set(fallbacks.map((f) => f.id));
|
|
1295
|
+
const hasOwnCoordinates = options.variant === "bubble" && options.getCoordinates !== void 0;
|
|
1296
|
+
const joinReport = {
|
|
1297
|
+
unmatchedData: hasOwnCoordinates ? [] : unmatchedIds.filter((id) => !fallbackIds.has(id)),
|
|
1298
|
+
featuresWithoutData: hasOwnCoordinates ? [] : join.featuresWithoutData,
|
|
1299
|
+
duplicateDataIds: join.duplicateDataIds,
|
|
1300
|
+
outOfRegion: hasOwnCoordinates ? [] : outOfRegion
|
|
1301
|
+
};
|
|
1302
|
+
const entries = features.features.map((feature) => {
|
|
1303
|
+
const id = idOf(feature);
|
|
1304
|
+
return { id, datum: id === void 0 ? void 0 : join.joined.get(id) };
|
|
1305
|
+
});
|
|
1306
|
+
const allEntries = [...entries, ...fallbacks.map(({ id, datum }) => ({ id, datum }))];
|
|
1307
|
+
const formatValue = options.formatValue ?? defaultFormatValue;
|
|
1308
|
+
const emptyFill = options.emptyFill ?? EMPTY_FILL_VAR;
|
|
1309
|
+
const variant = resolveVariant({
|
|
1310
|
+
config: options,
|
|
1311
|
+
entries: allEntries,
|
|
1312
|
+
data,
|
|
1313
|
+
getId,
|
|
1314
|
+
centroids: [...anchors, ...fallbacks.map((f) => f.at)],
|
|
1315
|
+
project,
|
|
1316
|
+
emptyFill,
|
|
1317
|
+
formatValue,
|
|
1318
|
+
emptyLabel: options.emptyLabel
|
|
1319
|
+
});
|
|
1320
|
+
const levelInfo = engine.active ? { id: engine.level?.id ?? ROOT_LEVEL.id, depth: engine.depth } : ROOT_LEVEL;
|
|
1321
|
+
const regions = [];
|
|
1322
|
+
const anonymousPaths = [];
|
|
1323
|
+
features.features.forEach((feature, index) => {
|
|
1324
|
+
const path = paths[index];
|
|
1325
|
+
const entry = entries[index];
|
|
1326
|
+
if (path == null) return;
|
|
1327
|
+
if (entry?.id === void 0) {
|
|
1328
|
+
anonymousPaths.push(path);
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
const datum = entry.datum;
|
|
1332
|
+
let wave;
|
|
1333
|
+
if (engine.active && machine.phase === "revealing") {
|
|
1334
|
+
const [cx2, cy] = toPath.centroid(feature);
|
|
1335
|
+
const reach = Math.hypot(width, height) / 2;
|
|
1336
|
+
wave = Number.isFinite(cx2) && Number.isFinite(cy) ? Math.min(1, Math.hypot(cx2 - width / 2, cy - height / 2) / reach) : 0;
|
|
1337
|
+
}
|
|
1338
|
+
const base = {
|
|
1339
|
+
id: entry.id,
|
|
1340
|
+
feature,
|
|
1341
|
+
datum,
|
|
1342
|
+
path,
|
|
1343
|
+
value: variant.values[index] ?? null,
|
|
1344
|
+
formattedValue: variant.labels[index] ?? "",
|
|
1345
|
+
fill: variant.fills[index] ?? "",
|
|
1346
|
+
wave,
|
|
1347
|
+
opacity: void 0,
|
|
1348
|
+
className: void 0,
|
|
1349
|
+
pattern: void 0,
|
|
1350
|
+
classIndex: variant.classes[index] ?? null,
|
|
1351
|
+
drillable: engine.active && isDrillable(engine.levels, engine.depth, {
|
|
1352
|
+
kind: "region",
|
|
1353
|
+
id: entry.id,
|
|
1354
|
+
feature,
|
|
1355
|
+
datum,
|
|
1356
|
+
level: levelInfo,
|
|
1357
|
+
drillable: true
|
|
1358
|
+
})
|
|
1359
|
+
};
|
|
1360
|
+
const custom = options.getRegionStyle?.(base);
|
|
1361
|
+
regions.push(
|
|
1362
|
+
custom === void 0 ? base : {
|
|
1363
|
+
...base,
|
|
1364
|
+
fill: custom.fill ?? base.fill,
|
|
1365
|
+
opacity: custom.opacity,
|
|
1366
|
+
className: custom.className,
|
|
1367
|
+
pattern: custom.pattern
|
|
1368
|
+
}
|
|
1369
|
+
);
|
|
1370
|
+
});
|
|
1371
|
+
const fallbackCoordinates = new Map(fallbacks.map((f) => [f.id, f.coordinates]));
|
|
1372
|
+
const bubbles = isBubble ? variant.bubbles.map((bubble) => {
|
|
1373
|
+
const coordinates = fallbackCoordinates.get(bubble.id);
|
|
1374
|
+
return bubble.coordinates === null && coordinates !== void 0 ? { ...bubble, coordinates } : bubble;
|
|
1375
|
+
}) : variant.bubbles;
|
|
1376
|
+
const fallbackMarks = isBubble ? [] : fallbacks.map((f, index) => {
|
|
1377
|
+
const at = features.features.length + index;
|
|
1378
|
+
return {
|
|
1379
|
+
id: f.id,
|
|
1380
|
+
x: f.at[0],
|
|
1381
|
+
y: f.at[1],
|
|
1382
|
+
r: FALLBACK_POINT_RADIUS,
|
|
1383
|
+
fill: variant.fills[at] ?? emptyFill,
|
|
1384
|
+
datum: f.datum,
|
|
1385
|
+
value: variant.values[at] ?? null,
|
|
1386
|
+
formattedValue: variant.labels[at] ?? "",
|
|
1387
|
+
coordinates: f.coordinates
|
|
1388
|
+
};
|
|
1389
|
+
});
|
|
1390
|
+
const reportKey = [
|
|
1391
|
+
data.length,
|
|
1392
|
+
joinReport.unmatchedData.join(""),
|
|
1393
|
+
joinReport.featuresWithoutData.join(""),
|
|
1394
|
+
joinReport.duplicateDataIds.join(""),
|
|
1395
|
+
joinReport.outOfRegion.join("")
|
|
1396
|
+
].join("\0");
|
|
1397
|
+
const { onJoinReport, diagnostics = true } = options;
|
|
1398
|
+
useEffect6(() => {
|
|
1399
|
+
if (status !== "ready") return;
|
|
1400
|
+
onJoinReport?.(joinReport);
|
|
1401
|
+
if (!diagnostics || isProduction()) return;
|
|
1402
|
+
const message = formatJoinDiagnostics({ ...joinReport, dataCount: data.length });
|
|
1403
|
+
const levelHint = formatWrongLevelDataHint({
|
|
1404
|
+
drillActive: engine.active,
|
|
1405
|
+
dataIsSelector: typeof rawOptions.data === "function",
|
|
1406
|
+
dataCount: data.length,
|
|
1407
|
+
unmatchedCount: joinReport.unmatchedData.length,
|
|
1408
|
+
levelId: engine.level?.id ?? null
|
|
1409
|
+
});
|
|
1410
|
+
if (message !== null) {
|
|
1411
|
+
console.warn(levelHint === null ? message : `${message}
|
|
1412
|
+
${levelHint}`);
|
|
1413
|
+
}
|
|
1414
|
+
}, [reportKey, status]);
|
|
1415
|
+
const emptyRegion = region !== void 0 && regionDrawn && features.features.length === 0;
|
|
1416
|
+
useEffect6(() => {
|
|
1417
|
+
if (status === "ready" && emptyRegion && diagnostics && !isProduction()) {
|
|
1418
|
+
console.warn(
|
|
1419
|
+
`[choropleth] region "${region?.key}" matches no region of the geometry. Regions are matched by ISO 3166-1 alpha-2 code against \`feature.id\`: if your geometry uses other ids, pass \`getFeatureId\`.`
|
|
1420
|
+
);
|
|
1421
|
+
}
|
|
1422
|
+
}, [emptyRegion, status, diagnostics, region?.key]);
|
|
1423
|
+
const noIdsMessage = formatNoIdsDiagnostic({
|
|
1424
|
+
featureCount: features.features.length,
|
|
1425
|
+
idCount: regions.length,
|
|
1426
|
+
levelId: engine.level?.id ?? null,
|
|
1427
|
+
customGetFeatureId: getFeatureId !== void 0
|
|
1428
|
+
});
|
|
1429
|
+
useEffect6(() => {
|
|
1430
|
+
if (status === "ready" && noIdsMessage !== null && diagnostics && !isProduction()) {
|
|
1431
|
+
console.warn(noIdsMessage);
|
|
1432
|
+
}
|
|
1433
|
+
}, [noIdsMessage, status, diagnostics]);
|
|
1434
|
+
const { onSelectedIdChange } = options;
|
|
1435
|
+
const controlled = options.selectedId !== void 0;
|
|
1436
|
+
const selectable = controlled || options.defaultSelectedId !== void 0 || onSelectedIdChange !== void 0;
|
|
1437
|
+
const [internalSelectedId, setInternalSelectedId] = useState5(
|
|
1438
|
+
options.defaultSelectedId ?? null
|
|
1439
|
+
);
|
|
1440
|
+
const selectedId = controlled ? options.selectedId ?? null : internalSelectedId;
|
|
1441
|
+
const [selectionLevel, setSelectionLevel] = useState5(engine.shownKey);
|
|
1442
|
+
if (selectionLevel !== engine.shownKey) {
|
|
1443
|
+
setSelectionLevel(engine.shownKey);
|
|
1444
|
+
if (!controlled && internalSelectedId !== null) setInternalSelectedId(null);
|
|
1445
|
+
}
|
|
1446
|
+
const setSelectedId = (id) => {
|
|
1447
|
+
if (!controlled) setInternalSelectedId(id);
|
|
1448
|
+
onSelectedIdChange?.(id);
|
|
1449
|
+
};
|
|
1450
|
+
const { onItemClick, onItemHover } = options;
|
|
1451
|
+
const { rememberFills } = engine;
|
|
1452
|
+
const drillActive = engine.active;
|
|
1453
|
+
useEffect6(() => {
|
|
1454
|
+
if (drillActive) rememberFills(new Map(regions.map((region2) => [region2.id, region2.fill])));
|
|
1455
|
+
});
|
|
1456
|
+
const regionsById = new Map(regions.map((region2) => [region2.id, region2]));
|
|
1457
|
+
const featuresById = /* @__PURE__ */ new Map();
|
|
1458
|
+
features.features.forEach((feature) => {
|
|
1459
|
+
const id = idOf(feature);
|
|
1460
|
+
if (id !== void 0) featuresById.set(id, feature);
|
|
1461
|
+
});
|
|
1462
|
+
const bubblesById = new Map(bubbles.filter((b) => b.coordinates !== null).map((b) => [b.id, b]));
|
|
1463
|
+
const fallbackMarksById = new Map(fallbackMarks.map((f) => [f.id, f]));
|
|
1464
|
+
const getItemContext = (ref) => {
|
|
1465
|
+
if (ref.kind === "region") {
|
|
1466
|
+
const region2 = regionsById.get(ref.id);
|
|
1467
|
+
if (region2 === void 0) return void 0;
|
|
1468
|
+
return {
|
|
1469
|
+
kind: "region",
|
|
1470
|
+
id: region2.id,
|
|
1471
|
+
feature: region2.feature,
|
|
1472
|
+
datum: region2.datum,
|
|
1473
|
+
level: levelInfo,
|
|
1474
|
+
drillable: region2.drillable,
|
|
1475
|
+
value: region2.value,
|
|
1476
|
+
formattedValue: region2.formattedValue,
|
|
1477
|
+
color: region2.fill,
|
|
1478
|
+
label: nameOf(region2.feature) ?? region2.id
|
|
1479
|
+
};
|
|
1480
|
+
}
|
|
1481
|
+
const bubble = bubblesById.get(ref.id);
|
|
1482
|
+
const mark = bubble === void 0 ? fallbackMarksById.get(ref.id) : void 0;
|
|
1483
|
+
const coordinates = bubble?.coordinates ?? mark?.coordinates;
|
|
1484
|
+
const source = bubble ?? mark;
|
|
1485
|
+
if (source === void 0 || coordinates == null) return void 0;
|
|
1486
|
+
const feature = featuresById.get(source.id);
|
|
1487
|
+
const value = bubble === void 0 ? mark?.value ?? null : bubble.value;
|
|
1488
|
+
return {
|
|
1489
|
+
kind: "point",
|
|
1490
|
+
id: source.id,
|
|
1491
|
+
coordinates,
|
|
1492
|
+
feature,
|
|
1493
|
+
datum: source.datum,
|
|
1494
|
+
level: levelInfo,
|
|
1495
|
+
drillable: false,
|
|
1496
|
+
value,
|
|
1497
|
+
formattedValue: bubble === void 0 ? mark?.formattedValue ?? "" : formatValue(bubble.value),
|
|
1498
|
+
color: bubble === void 0 ? mark?.fill ?? emptyFill : bubble.fill,
|
|
1499
|
+
label: (feature === void 0 ? void 0 : nameOf(feature)) ?? source.id
|
|
1500
|
+
};
|
|
1501
|
+
};
|
|
1502
|
+
const previewed = useRef5(null);
|
|
1503
|
+
const hoverItem = (ref) => {
|
|
1504
|
+
if (isSameItem(store.getState().hovered, ref)) return;
|
|
1505
|
+
if (!isSameItem(previewed.current, ref)) previewed.current = null;
|
|
1506
|
+
store.setState({ hovered: ref });
|
|
1507
|
+
onItemHover?.(ref === null ? null : getItemContext(ref) ?? null);
|
|
1508
|
+
};
|
|
1509
|
+
const clickItem = (ref) => {
|
|
1510
|
+
const item = getItemContext(ref);
|
|
1511
|
+
if (item === void 0) return;
|
|
1512
|
+
if (ref.kind === "region" && item.drillable && engine.controller !== null) {
|
|
1513
|
+
if (store.getState().pointerType === "touch" && !isSameItem(previewed.current, ref)) {
|
|
1514
|
+
hoverItem(ref);
|
|
1515
|
+
previewed.current = ref;
|
|
1516
|
+
return;
|
|
1517
|
+
}
|
|
1518
|
+
previewed.current = null;
|
|
1519
|
+
engine.noteItem(item);
|
|
1520
|
+
engine.controller.drillTo([...engine.shownPath, ref.id]);
|
|
1521
|
+
} else if (ref.kind === "region" && selectable) {
|
|
1522
|
+
setSelectedId(ref.id === selectedId ? null : ref.id);
|
|
1523
|
+
}
|
|
1524
|
+
onItemClick?.(item);
|
|
1525
|
+
};
|
|
1526
|
+
const { onDrillIntent } = options;
|
|
1527
|
+
const intentRef = useRef5({ regionsById, shownPath: engine.shownPath, idle: true, onDrillIntent });
|
|
1528
|
+
const { prefetch } = engine;
|
|
1529
|
+
useEffect6(() => {
|
|
1530
|
+
intentRef.current = {
|
|
1531
|
+
regionsById,
|
|
1532
|
+
shownPath: engine.shownPath,
|
|
1533
|
+
idle: engine.machine.phase === "idle",
|
|
1534
|
+
onDrillIntent
|
|
1535
|
+
};
|
|
1536
|
+
});
|
|
1537
|
+
useEffect6(() => {
|
|
1538
|
+
if (!engine.active) return;
|
|
1539
|
+
const timer = createIntentTimer({
|
|
1540
|
+
onIntent: (id) => {
|
|
1541
|
+
const current = intentRef.current;
|
|
1542
|
+
if (!current.idle) return;
|
|
1543
|
+
const path = [...current.shownPath, id];
|
|
1544
|
+
prefetch(path);
|
|
1545
|
+
current.onDrillIntent?.(contextOf(path));
|
|
1546
|
+
}
|
|
1547
|
+
});
|
|
1548
|
+
const sync = () => {
|
|
1549
|
+
const hovered = store.getState().hovered;
|
|
1550
|
+
const drillable = hovered?.kind === "region" && intentRef.current.regionsById.get(hovered.id)?.drillable;
|
|
1551
|
+
timer.hover(hovered !== null && drillable === true ? hovered.id : null);
|
|
1552
|
+
};
|
|
1553
|
+
sync();
|
|
1554
|
+
const unsubscribe = store.subscribe(sync);
|
|
1555
|
+
return () => {
|
|
1556
|
+
unsubscribe();
|
|
1557
|
+
timer.dispose();
|
|
1558
|
+
};
|
|
1559
|
+
}, [engine.active, store, prefetch]);
|
|
1560
|
+
const findRequestProblem = (path) => {
|
|
1561
|
+
const shown = engine.shownPath;
|
|
1562
|
+
for (let depth = 0; depth < path.length; depth++) {
|
|
1563
|
+
if (depth > shown.length || depth > 0 && path[depth - 1] !== shown[depth - 1]) break;
|
|
1564
|
+
const id = path[depth];
|
|
1565
|
+
const level = engine.chain[depth];
|
|
1566
|
+
const knownIds = level === void 0 ? void 0 : new Set(level.features.flatMap((feature) => level.idOf(feature) ?? []));
|
|
1567
|
+
const problem = checkDrillStep({
|
|
1568
|
+
id,
|
|
1569
|
+
depth,
|
|
1570
|
+
levelId: engine.levels[depth]?.id ?? "?",
|
|
1571
|
+
knownIds,
|
|
1572
|
+
drillable: depth === shown.length && !dataLoading ? regionsById.get(id)?.drillable : void 0
|
|
1573
|
+
});
|
|
1574
|
+
if (problem !== null) return problem;
|
|
1575
|
+
}
|
|
1576
|
+
return null;
|
|
1577
|
+
};
|
|
1578
|
+
const latestDrill = useRef5({ findRequestProblem, engine });
|
|
1579
|
+
useEffect6(() => {
|
|
1580
|
+
latestDrill.current = { findRequestProblem, engine };
|
|
1581
|
+
});
|
|
1582
|
+
const drillApi = useMemo4(() => {
|
|
1583
|
+
const refuse = (message) => {
|
|
1584
|
+
if (!isProduction()) console.warn(message);
|
|
1585
|
+
return Promise.resolve({ completed: false });
|
|
1586
|
+
};
|
|
1587
|
+
const go = (path, source) => {
|
|
1588
|
+
const { findRequestProblem: find, engine: current } = latestDrill.current;
|
|
1589
|
+
if (clampPath(path, current.levels.length).clamped) {
|
|
1590
|
+
return refuse(formatTooDeepRequest(path, current.levels.length, source));
|
|
1591
|
+
}
|
|
1592
|
+
const problem = find(path);
|
|
1593
|
+
if (problem !== null) return refuse(formatDrillRequestProblem(problem, source));
|
|
1594
|
+
return current.navigate((controller2) => controller2.drillTo(path));
|
|
1595
|
+
};
|
|
1596
|
+
return {
|
|
1597
|
+
drillDown: (id) => go([...latestDrill.current.engine.target, id], "drillDown()"),
|
|
1598
|
+
drillUp: (steps) => latestDrill.current.engine.navigate((controller2) => controller2.drillUp(steps)),
|
|
1599
|
+
drillTo: (path) => go(path, "drillTo()")
|
|
1600
|
+
};
|
|
1601
|
+
}, []);
|
|
1602
|
+
useEffect6(() => {
|
|
1603
|
+
if (!engine.active) return;
|
|
1604
|
+
store.registerDrill(drillApi);
|
|
1605
|
+
return () => store.registerDrill(null);
|
|
1606
|
+
}, [engine.active, store, drillApi]);
|
|
1607
|
+
const targetKey = JSON.stringify(engine.target);
|
|
1608
|
+
useEffect6(() => {
|
|
1609
|
+
if (!engine.active || isProduction()) return;
|
|
1610
|
+
const problem = latestDrill.current.findRequestProblem(engine.target);
|
|
1611
|
+
if (problem !== null) console.warn(formatDrillRequestProblem(problem, "drill path"));
|
|
1612
|
+
}, [engine.active, targetKey]);
|
|
1613
|
+
const leaveHandler = (ref) => (event) => {
|
|
1614
|
+
if (event.pointerType !== "touch" && isSameItem(store.getState().hovered, ref)) {
|
|
1615
|
+
hoverItem(null);
|
|
1616
|
+
}
|
|
1617
|
+
};
|
|
1618
|
+
const getRegionProps = (region2) => {
|
|
1619
|
+
const ref = { kind: "region", id: region2.id };
|
|
1620
|
+
return {
|
|
1621
|
+
"data-region-id": region2.id,
|
|
1622
|
+
"data-selected": region2.id === selectedId ? true : void 0,
|
|
1623
|
+
onClick: () => clickItem(ref),
|
|
1624
|
+
onPointerEnter: () => hoverItem(ref),
|
|
1625
|
+
onPointerLeave: leaveHandler(ref)
|
|
1626
|
+
};
|
|
1627
|
+
};
|
|
1628
|
+
const getPointProps = (point) => {
|
|
1629
|
+
const ref = { kind: "point", id: point.id };
|
|
1630
|
+
return {
|
|
1631
|
+
"data-point-id": point.id,
|
|
1632
|
+
onClick: () => clickItem(ref),
|
|
1633
|
+
onPointerEnter: () => hoverItem(ref),
|
|
1634
|
+
onPointerLeave: leaveHandler(ref)
|
|
1635
|
+
};
|
|
1636
|
+
};
|
|
1637
|
+
const shownKey = engine.shownKey;
|
|
1638
|
+
const previousLevel = useRef5(shownKey);
|
|
1639
|
+
useIsoLayoutEffect(() => {
|
|
1640
|
+
if (!engine.active || previousLevel.current === shownKey) return;
|
|
1641
|
+
previousLevel.current = shownKey;
|
|
1642
|
+
const from = chain?.projections[chain.projections.length - 1];
|
|
1643
|
+
const to = previousChain?.projections[previousChain.projections.length - 1];
|
|
1644
|
+
const camera = machine.direction === "up" && from !== void 0 && to !== void 0 ? relativeTransform(from, to) : FIT_TRANSFORM;
|
|
1645
|
+
void store.getZoom()?.flyTo(camera, { duration: 0, silent: true });
|
|
1646
|
+
}, [engine.active, shownKey, store]);
|
|
1647
|
+
const zoomOptions = resolveZoomOptions(options.zoom, {
|
|
1648
|
+
// With `drill` the click already does something (go down a level): the double click must NOT also zoom
|
|
1649
|
+
// ×2, or the second click would interrupt the drill flight and end up as a plain zoom.
|
|
1650
|
+
clickIsBusy: onItemClick !== void 0 || selectable || engine.active
|
|
1651
|
+
});
|
|
1652
|
+
const focusBounds = { ...DEFAULT_FOCUS_BOUNDS, ...options.focusBounds };
|
|
1653
|
+
const getRegionExtent = (id) => {
|
|
1654
|
+
const feature = featuresById.get(id);
|
|
1655
|
+
return feature === void 0 ? null : getFocusExtent({ feature, path: toPath, project });
|
|
1656
|
+
};
|
|
1657
|
+
const resolveCamera = (target, cameraOptions) => {
|
|
1658
|
+
if (zoomOptions === null) return null;
|
|
1659
|
+
const size = { width, height };
|
|
1660
|
+
const transform = resolveZoomTarget({
|
|
1661
|
+
target,
|
|
1662
|
+
size,
|
|
1663
|
+
padding: cameraOptions?.padding ?? zoomOptions.padding,
|
|
1664
|
+
scaleExtent: [zoomOptions.min, zoomOptions.max],
|
|
1665
|
+
project,
|
|
1666
|
+
getRegionExtent,
|
|
1667
|
+
focusBounds
|
|
1668
|
+
});
|
|
1669
|
+
return transform !== null && zoomOptions.constrain === "contain" ? constrainTransform({
|
|
1670
|
+
transform,
|
|
1671
|
+
content: [
|
|
1672
|
+
[0, 0],
|
|
1673
|
+
[width, height]
|
|
1674
|
+
],
|
|
1675
|
+
view: [
|
|
1676
|
+
[0, 0],
|
|
1677
|
+
[width, height]
|
|
1678
|
+
]
|
|
1679
|
+
}) : transform;
|
|
1680
|
+
};
|
|
1681
|
+
return {
|
|
1682
|
+
containerRef: measured.ref,
|
|
1683
|
+
width,
|
|
1684
|
+
height,
|
|
1685
|
+
drillPhase: engine.active ? machine.phase : null,
|
|
1686
|
+
drillFocusId,
|
|
1687
|
+
drillLayers,
|
|
1688
|
+
drillNav,
|
|
1689
|
+
selectContextItem: (depth, id) => {
|
|
1690
|
+
engine.controller?.drillTo([...engine.shownPath.slice(0, depth), id]);
|
|
1691
|
+
},
|
|
1692
|
+
status,
|
|
1693
|
+
dataLoading,
|
|
1694
|
+
regions,
|
|
1695
|
+
anonymousPaths,
|
|
1696
|
+
emptyFill,
|
|
1697
|
+
fallbackPoints: fallbackMarks,
|
|
1698
|
+
labels,
|
|
1699
|
+
zoom: zoomOptions,
|
|
1700
|
+
onViewChange: typeof options.zoom === "object" ? options.zoom.onViewChange : void 0,
|
|
1701
|
+
invert,
|
|
1702
|
+
project,
|
|
1703
|
+
focusId: options.focusId,
|
|
1704
|
+
resolveCamera,
|
|
1705
|
+
borders: bordersPath,
|
|
1706
|
+
legend: variant.legend,
|
|
1707
|
+
sizeLegend: variant.sizeLegend,
|
|
1708
|
+
bubbles,
|
|
1709
|
+
scale: variant.scale,
|
|
1710
|
+
joinReport,
|
|
1711
|
+
formatValue,
|
|
1712
|
+
clickable: onItemClick !== void 0 || selectable,
|
|
1713
|
+
store,
|
|
1714
|
+
selectedId,
|
|
1715
|
+
setSelectedId,
|
|
1716
|
+
hoverItem,
|
|
1717
|
+
clickItem,
|
|
1718
|
+
getItemContext,
|
|
1719
|
+
getRegionProps,
|
|
1720
|
+
getPointProps
|
|
1721
|
+
};
|
|
1722
|
+
};
|
|
1723
|
+
var useIsoLayoutEffect = typeof window === "undefined" ? useEffect6 : useLayoutEffect;
|
|
1724
|
+
var NO_GEOGRAPHY = { type: "FeatureCollection", features: [] };
|
|
1725
|
+
var EMPTY_FEATURES = { type: "FeatureCollection", features: [] };
|
|
1726
|
+
var NO_DATA = [];
|
|
1727
|
+
var EMPTY_FILL_VAR = `var(--cm-empty-fill, ${DEFAULT_EMPTY_FILL})`;
|
|
1728
|
+
var nameOf = (feature) => {
|
|
1729
|
+
const name = feature.properties?.name;
|
|
1730
|
+
return typeof name === "string" ? name : void 0;
|
|
1731
|
+
};
|
|
1732
|
+
var defaultFeatureId2 = (feature) => feature.id === void 0 ? void 0 : String(feature.id);
|
|
1733
|
+
|
|
1734
|
+
// src/react/choropleth-context.ts
|
|
1735
|
+
import { createContext, useContext } from "react";
|
|
1736
|
+
var ChoroplethContext = createContext(null);
|
|
1737
|
+
var useChoroplethContext = () => {
|
|
1738
|
+
const value = useContext(ChoroplethContext);
|
|
1739
|
+
if (value === null) {
|
|
1740
|
+
throw new Error(
|
|
1741
|
+
"[choropleth] Choropleth components (Svg, Regions, Legend\u2026) must be rendered inside <Choropleth.Root>."
|
|
1742
|
+
);
|
|
1743
|
+
}
|
|
1744
|
+
return value;
|
|
1745
|
+
};
|
|
1746
|
+
var useChoroplethApi = () => useChoroplethContext().api;
|
|
1747
|
+
var useDefId = (name) => useChoroplethContext().defId(name);
|
|
1748
|
+
|
|
1749
|
+
// src/react/components/defs.tsx
|
|
1750
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
1751
|
+
var Defs = ({ children }) => {
|
|
1752
|
+
const { defId } = useChoroplethContext();
|
|
1753
|
+
const color = {
|
|
1754
|
+
color: "var(--cm-pattern-color, color-mix(in srgb, var(--cm-label-color, #111111) 45%, transparent))"
|
|
1755
|
+
};
|
|
1756
|
+
return /* @__PURE__ */ jsxs("defs", { children: [
|
|
1757
|
+
/* @__PURE__ */ jsx(
|
|
1758
|
+
"pattern",
|
|
1759
|
+
{
|
|
1760
|
+
id: defId("hatch"),
|
|
1761
|
+
width: "6",
|
|
1762
|
+
height: "6",
|
|
1763
|
+
patternUnits: "userSpaceOnUse",
|
|
1764
|
+
patternTransform: "rotate(45)",
|
|
1765
|
+
style: color,
|
|
1766
|
+
children: /* @__PURE__ */ jsx("line", { x1: "0", y1: "0", x2: "0", y2: "6", stroke: "currentColor", strokeWidth: "2" })
|
|
1767
|
+
}
|
|
1768
|
+
),
|
|
1769
|
+
/* @__PURE__ */ jsx("pattern", { id: defId("dots"), width: "6", height: "6", patternUnits: "userSpaceOnUse", style: color, children: /* @__PURE__ */ jsx("circle", { cx: "3", cy: "3", r: "1.2", fill: "currentColor" }) }),
|
|
1770
|
+
children
|
|
1771
|
+
] });
|
|
1772
|
+
};
|
|
1773
|
+
|
|
1774
|
+
// src/react/components/patterns.tsx
|
|
1775
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
1776
|
+
var Patterns = () => {
|
|
1777
|
+
const { choropleth, defId, emptyPattern } = useChoroplethContext();
|
|
1778
|
+
const marks = choropleth.regions.flatMap((region) => {
|
|
1779
|
+
const name = region.pattern ?? (emptyPattern !== null && region.value === null ? emptyPattern : void 0);
|
|
1780
|
+
return name === void 0 ? [] : [{ id: region.id, d: region.path, name }];
|
|
1781
|
+
});
|
|
1782
|
+
if (marks.length === 0) return null;
|
|
1783
|
+
return /* @__PURE__ */ jsx2("g", { className: "cm-patterns", pointerEvents: "none", "aria-hidden": "true", children: marks.map(({ id, d, name }) => /* @__PURE__ */ jsx2(
|
|
1784
|
+
"path",
|
|
1785
|
+
{
|
|
1786
|
+
d,
|
|
1787
|
+
fill: `url(#${defId(name)})`
|
|
1788
|
+
},
|
|
1789
|
+
id
|
|
1790
|
+
)) });
|
|
1791
|
+
};
|
|
1792
|
+
|
|
1793
|
+
// src/react/use-camera.ts
|
|
1794
|
+
import { useMemo as useMemo5, useSyncExternalStore as useSyncExternalStore3 } from "react";
|
|
1795
|
+
var useCamera = (selector) => {
|
|
1796
|
+
const { store, api } = useChoroplethContext();
|
|
1797
|
+
const getSnapshot = useMemo5(
|
|
1798
|
+
() => createCameraSnapshot(store, () => api.getView(), selector),
|
|
1799
|
+
[store, api, selector]
|
|
1800
|
+
);
|
|
1801
|
+
return useSyncExternalStore3(store.subscribe, getSnapshot, getSnapshot);
|
|
1802
|
+
};
|
|
1803
|
+
var createCameraSnapshot = (store, getView, selector) => {
|
|
1804
|
+
let lastCamera;
|
|
1805
|
+
let last;
|
|
1806
|
+
return () => {
|
|
1807
|
+
const camera = store.getState().camera;
|
|
1808
|
+
if (last !== void 0 && Object.is(camera, lastCamera)) return last.value;
|
|
1809
|
+
const value = selector(getView());
|
|
1810
|
+
lastCamera = camera;
|
|
1811
|
+
if (last === void 0 || !shallowEqual(last.value, value)) last = { value };
|
|
1812
|
+
return last.value;
|
|
1813
|
+
};
|
|
1814
|
+
};
|
|
1815
|
+
var shallowEqual = (a, b) => {
|
|
1816
|
+
if (Object.is(a, b)) return true;
|
|
1817
|
+
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
|
|
1818
|
+
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
1819
|
+
const keysA = Object.keys(a);
|
|
1820
|
+
if (keysA.length !== Object.keys(b).length) return false;
|
|
1821
|
+
return keysA.every(
|
|
1822
|
+
(key) => Object.hasOwn(b, key) && Object.is(a[key], b[key])
|
|
1823
|
+
);
|
|
1824
|
+
};
|
|
1825
|
+
|
|
1826
|
+
// src/react/use-zoom.ts
|
|
1827
|
+
import { useEffect as useEffect7, useRef as useRef6 } from "react";
|
|
1828
|
+
import { easeCubicInOut, easeCubicOut } from "d3-ease";
|
|
1829
|
+
import { select } from "d3-selection";
|
|
1830
|
+
import "d3-transition";
|
|
1831
|
+
import { zoom, zoomIdentity, zoomTransform } from "d3-zoom";
|
|
1832
|
+
|
|
1833
|
+
// src/react/fade.ts
|
|
1834
|
+
var FADE_OUT_MS = 100;
|
|
1835
|
+
var FADE_IN_MS = 150;
|
|
1836
|
+
var fadeThrough = (element, jump) => {
|
|
1837
|
+
if (typeof element.animate !== "function") {
|
|
1838
|
+
jump();
|
|
1839
|
+
return { promise: Promise.resolve({ completed: true }), cancel: () => {
|
|
1840
|
+
} };
|
|
1841
|
+
}
|
|
1842
|
+
const animations = [];
|
|
1843
|
+
const out = element.animate([{ opacity: 1 }, { opacity: 0 }], {
|
|
1844
|
+
duration: FADE_OUT_MS,
|
|
1845
|
+
fill: "forwards"
|
|
1846
|
+
});
|
|
1847
|
+
animations.push(out);
|
|
1848
|
+
const promise = out.finished.then(() => {
|
|
1849
|
+
jump();
|
|
1850
|
+
const back = element.animate([{ opacity: 0 }, { opacity: 1 }], { duration: FADE_IN_MS });
|
|
1851
|
+
animations.push(back);
|
|
1852
|
+
out.cancel();
|
|
1853
|
+
return back.finished;
|
|
1854
|
+
}).then(
|
|
1855
|
+
() => ({ completed: true }),
|
|
1856
|
+
() => ({ completed: false })
|
|
1857
|
+
);
|
|
1858
|
+
return { promise, cancel: () => animations.forEach((animation) => animation.cancel()) };
|
|
1859
|
+
};
|
|
1860
|
+
|
|
1861
|
+
// src/react/use-zoom.ts
|
|
1862
|
+
var BUTTON_ZOOM_MS = 300;
|
|
1863
|
+
var isPressOnDrillTarget = (event, root) => {
|
|
1864
|
+
if (event.type !== "mousedown" && event.type !== "touchstart") return false;
|
|
1865
|
+
const phase = root.getAttribute("data-drill-phase");
|
|
1866
|
+
if (phase !== "flying" && phase !== "loading") return false;
|
|
1867
|
+
const target = event.target;
|
|
1868
|
+
return target instanceof Element && target.closest("[data-drill-target]") !== null;
|
|
1869
|
+
};
|
|
1870
|
+
var CLICK_DISTANCE_PX = 4;
|
|
1871
|
+
var ZOOMED_EPSILON = 1.001;
|
|
1872
|
+
var useZoom = ({
|
|
1873
|
+
svgRef,
|
|
1874
|
+
viewportRef,
|
|
1875
|
+
rootRef,
|
|
1876
|
+
store,
|
|
1877
|
+
width,
|
|
1878
|
+
height,
|
|
1879
|
+
options,
|
|
1880
|
+
invert,
|
|
1881
|
+
project,
|
|
1882
|
+
onViewChange,
|
|
1883
|
+
resolveCamera,
|
|
1884
|
+
focusId,
|
|
1885
|
+
ready
|
|
1886
|
+
}) => {
|
|
1887
|
+
const latest = useRef6({ invert, project, onViewChange, resolveCamera });
|
|
1888
|
+
useEffect7(() => {
|
|
1889
|
+
latest.current = { invert, project, onViewChange, resolveCamera };
|
|
1890
|
+
});
|
|
1891
|
+
const previous = useRef6(null);
|
|
1892
|
+
const enabled = options !== null;
|
|
1893
|
+
const {
|
|
1894
|
+
min,
|
|
1895
|
+
max,
|
|
1896
|
+
wheel,
|
|
1897
|
+
pinch,
|
|
1898
|
+
pan,
|
|
1899
|
+
doubleClick,
|
|
1900
|
+
constrain,
|
|
1901
|
+
initialView,
|
|
1902
|
+
bubbleScaling,
|
|
1903
|
+
inertia
|
|
1904
|
+
} = options ?? NO_ZOOM;
|
|
1905
|
+
useEffect7(() => {
|
|
1906
|
+
const svg = svgRef.current;
|
|
1907
|
+
const viewport = viewportRef.current;
|
|
1908
|
+
const root = rootRef.current;
|
|
1909
|
+
if (!enabled || !ready || svg === null || viewport === null || root === null) return;
|
|
1910
|
+
const size = { width, height };
|
|
1911
|
+
const content = [
|
|
1912
|
+
[0, 0],
|
|
1913
|
+
[width, height]
|
|
1914
|
+
];
|
|
1915
|
+
let restoring = false;
|
|
1916
|
+
let samples = [];
|
|
1917
|
+
let glide = null;
|
|
1918
|
+
let gesture = { k: 1, x: 0, y: 0, moving: false };
|
|
1919
|
+
const commit = (transform, notify) => {
|
|
1920
|
+
const camera = { k: transform.k, x: transform.x, y: transform.y };
|
|
1921
|
+
store.setState({ camera, interacting: false });
|
|
1922
|
+
if (!notify) return;
|
|
1923
|
+
latest.current.onViewChange?.(
|
|
1924
|
+
toViewState({
|
|
1925
|
+
transform: camera,
|
|
1926
|
+
size,
|
|
1927
|
+
invert: latest.current.invert,
|
|
1928
|
+
level: ROOT_LEVEL
|
|
1929
|
+
})
|
|
1930
|
+
);
|
|
1931
|
+
};
|
|
1932
|
+
const stopGlide = () => {
|
|
1933
|
+
if (glide === null) return;
|
|
1934
|
+
cancelAnimationFrame(glide.frame);
|
|
1935
|
+
glide = null;
|
|
1936
|
+
commit(zoomTransform(svg), true);
|
|
1937
|
+
};
|
|
1938
|
+
const baseFilter = createZoomFilter({
|
|
1939
|
+
wheel,
|
|
1940
|
+
pinch,
|
|
1941
|
+
pan,
|
|
1942
|
+
doubleClick,
|
|
1943
|
+
onGestureHint: (kind) => store.showGestureHint(kind)
|
|
1944
|
+
});
|
|
1945
|
+
const behavior = zoom().extent([
|
|
1946
|
+
[0, 0],
|
|
1947
|
+
[width, height]
|
|
1948
|
+
]).scaleExtent([min, max]).clickDistance(CLICK_DISTANCE_PX).filter((event) => {
|
|
1949
|
+
if (isPressOnDrillTarget(event, root)) return false;
|
|
1950
|
+
return baseFilter(event);
|
|
1951
|
+
}).on("start", (event) => {
|
|
1952
|
+
if (event.sourceEvent === null) return;
|
|
1953
|
+
stopGlide();
|
|
1954
|
+
samples = [];
|
|
1955
|
+
gesture = {
|
|
1956
|
+
k: event.transform.k,
|
|
1957
|
+
x: event.transform.x,
|
|
1958
|
+
y: event.transform.y,
|
|
1959
|
+
moving: false
|
|
1960
|
+
};
|
|
1961
|
+
}).on(
|
|
1962
|
+
"zoom",
|
|
1963
|
+
({ transform, sourceEvent }) => {
|
|
1964
|
+
viewport.setAttribute("transform", transform.toString());
|
|
1965
|
+
root.style.setProperty("--cm-k", String(transform.k));
|
|
1966
|
+
root.style.setProperty(
|
|
1967
|
+
"--cm-bubble-scale",
|
|
1968
|
+
String(bubbleScale(transform.k, bubbleScaling))
|
|
1969
|
+
);
|
|
1970
|
+
if (sourceEvent !== null && !gesture.moving) {
|
|
1971
|
+
const moved = sourceEvent.type === "wheel" || transform.k !== gesture.k || Math.hypot(transform.x - gesture.x, transform.y - gesture.y) > CLICK_DISTANCE_PX;
|
|
1972
|
+
if (moved) {
|
|
1973
|
+
gesture.moving = true;
|
|
1974
|
+
store.setState({ interacting: true });
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
if (sourceEvent !== null) {
|
|
1978
|
+
samples.push({
|
|
1979
|
+
time: performance.now(),
|
|
1980
|
+
k: transform.k,
|
|
1981
|
+
x: transform.x,
|
|
1982
|
+
y: transform.y
|
|
1983
|
+
});
|
|
1984
|
+
if (samples.length > MAX_SAMPLES) samples.shift();
|
|
1985
|
+
}
|
|
1986
|
+
root.toggleAttribute("data-zoomed", transform.k > ZOOMED_EPSILON);
|
|
1987
|
+
}
|
|
1988
|
+
).on(
|
|
1989
|
+
"end",
|
|
1990
|
+
({ transform, sourceEvent }) => {
|
|
1991
|
+
if (glide !== null) return;
|
|
1992
|
+
const velocity = inertia && sourceEvent !== null && !restoring && !prefersReducedMotion() ? releaseVelocity({ samples, now: performance.now() }) : null;
|
|
1993
|
+
samples = [];
|
|
1994
|
+
if (velocity !== null) {
|
|
1995
|
+
store.setState({ camera: { k: transform.k, x: transform.x, y: transform.y } });
|
|
1996
|
+
startGlide(transform, velocity);
|
|
1997
|
+
return;
|
|
1998
|
+
}
|
|
1999
|
+
commit(transform, !restoring);
|
|
2000
|
+
}
|
|
2001
|
+
);
|
|
2002
|
+
if (constrain === "contain") {
|
|
2003
|
+
behavior.constrain((transform, extent) => {
|
|
2004
|
+
const c = constrainTransform({ transform, content, view: extent });
|
|
2005
|
+
return zoomIdentity.translate(c.x, c.y).scale(c.k);
|
|
2006
|
+
});
|
|
2007
|
+
}
|
|
2008
|
+
behavior.interpolate(createZoomInterpolator(DEFAULT_FLIGHT_CURVATURE));
|
|
2009
|
+
const selection = select(svg).call(behavior);
|
|
2010
|
+
if (wheel === "cooperative") svg.style.touchAction = "pan-x pan-y";
|
|
2011
|
+
let restored = store.getState().camera;
|
|
2012
|
+
const before = previous.current;
|
|
2013
|
+
if (before !== null && (before.size.width !== width || before.size.height !== height)) {
|
|
2014
|
+
const moved = preserveView({
|
|
2015
|
+
transform: before.camera,
|
|
2016
|
+
oldSize: before.size,
|
|
2017
|
+
newSize: size,
|
|
2018
|
+
invertOld: before.invert,
|
|
2019
|
+
projectNew: latest.current.project
|
|
2020
|
+
});
|
|
2021
|
+
restored = constrain === "contain" ? constrainTransform({ transform: moved, content, view: content }) : moved;
|
|
2022
|
+
}
|
|
2023
|
+
restoring = true;
|
|
2024
|
+
behavior.transform(selection, zoomIdentity.translate(restored.x, restored.y).scale(restored.k));
|
|
2025
|
+
restoring = false;
|
|
2026
|
+
previous.current = null;
|
|
2027
|
+
const startGlide = (origin, [vx, vy]) => {
|
|
2028
|
+
const start = performance.now();
|
|
2029
|
+
let last = origin;
|
|
2030
|
+
store.setState({ interacting: true });
|
|
2031
|
+
const step = () => {
|
|
2032
|
+
const elapsed = performance.now() - start;
|
|
2033
|
+
const wanted = {
|
|
2034
|
+
k: origin.k,
|
|
2035
|
+
x: origin.x + inertiaOffset({ velocity: vx, elapsed }),
|
|
2036
|
+
y: origin.y + inertiaOffset({ velocity: vy, elapsed })
|
|
2037
|
+
};
|
|
2038
|
+
const next = constrain === "contain" ? constrainTransform({ transform: wanted, content, view: content }) : wanted;
|
|
2039
|
+
behavior.transform(selection, zoomIdentity.translate(next.x, next.y).scale(next.k));
|
|
2040
|
+
const now = zoomTransform(svg);
|
|
2041
|
+
const moved = Math.hypot(now.x - last.x, now.y - last.y);
|
|
2042
|
+
last = now;
|
|
2043
|
+
const remaining = Math.hypot(vx, vy) * INERTIA_TAU_MS * Math.exp(-elapsed / INERTIA_TAU_MS);
|
|
2044
|
+
if (remaining < GLIDE_STOP_PX || elapsed > 0 && moved < 0.01) {
|
|
2045
|
+
glide = null;
|
|
2046
|
+
commit(now, true);
|
|
2047
|
+
return;
|
|
2048
|
+
}
|
|
2049
|
+
glide = { frame: requestAnimationFrame(step) };
|
|
2050
|
+
};
|
|
2051
|
+
glide = { frame: requestAnimationFrame(step) };
|
|
2052
|
+
};
|
|
2053
|
+
const onPointerDown = () => stopGlide();
|
|
2054
|
+
svg.addEventListener("pointerdown", onPointerDown);
|
|
2055
|
+
let fade = null;
|
|
2056
|
+
const animate = (duration, apply, {
|
|
2057
|
+
ease = easeCubicInOut,
|
|
2058
|
+
silent = false
|
|
2059
|
+
} = {}) => {
|
|
2060
|
+
fade?.cancel();
|
|
2061
|
+
fade = null;
|
|
2062
|
+
stopGlide();
|
|
2063
|
+
if (duration <= 0) {
|
|
2064
|
+
restoring = silent;
|
|
2065
|
+
apply(selection);
|
|
2066
|
+
restoring = false;
|
|
2067
|
+
return Promise.resolve({ completed: true });
|
|
2068
|
+
}
|
|
2069
|
+
if (prefersReducedMotion()) {
|
|
2070
|
+
fade = fadeThrough(viewport, () => apply(selection));
|
|
2071
|
+
return fade.promise;
|
|
2072
|
+
}
|
|
2073
|
+
const transition = selection.transition().duration(duration).ease(ease);
|
|
2074
|
+
apply(transition);
|
|
2075
|
+
return transition.end().then(
|
|
2076
|
+
() => ({ completed: true }),
|
|
2077
|
+
() => ({ completed: false })
|
|
2078
|
+
);
|
|
2079
|
+
};
|
|
2080
|
+
const toZoomIdentity = ({ k, x, y }) => zoomIdentity.translate(x, y).scale(k);
|
|
2081
|
+
const current = () => {
|
|
2082
|
+
const { k, x, y } = zoomTransform(svg);
|
|
2083
|
+
return { k, x, y };
|
|
2084
|
+
};
|
|
2085
|
+
const api = {
|
|
2086
|
+
zoomTo: (target, { duration, padding, silent } = {}) => {
|
|
2087
|
+
const destination = latest.current.resolveCamera(target, { padding });
|
|
2088
|
+
if (destination === null) {
|
|
2089
|
+
if (!isProduction()) {
|
|
2090
|
+
console.warn(
|
|
2091
|
+
`[choropleth] zoomTo: could not resolve ${JSON.stringify(target)} (unknown region id, or a point outside the projection).`
|
|
2092
|
+
);
|
|
2093
|
+
}
|
|
2094
|
+
return Promise.resolve({ completed: false });
|
|
2095
|
+
}
|
|
2096
|
+
const length = duration ?? flightDuration({ from: current(), to: destination, size });
|
|
2097
|
+
return animate(
|
|
2098
|
+
length,
|
|
2099
|
+
(t) => behavior.transform(t, toZoomIdentity(destination)),
|
|
2100
|
+
{
|
|
2101
|
+
silent
|
|
2102
|
+
}
|
|
2103
|
+
);
|
|
2104
|
+
},
|
|
2105
|
+
flyTo: (camera, { duration, silent } = {}) => {
|
|
2106
|
+
const length = duration ?? flightDuration({ from: current(), to: camera, size });
|
|
2107
|
+
return animate(
|
|
2108
|
+
length,
|
|
2109
|
+
(t) => behavior.transform(t, toZoomIdentity(camera)),
|
|
2110
|
+
{
|
|
2111
|
+
silent
|
|
2112
|
+
}
|
|
2113
|
+
);
|
|
2114
|
+
},
|
|
2115
|
+
zoomBy: (factor, { duration = BUTTON_ZOOM_MS } = {}) => animate(duration, (t) => behavior.scaleBy(t, factor), {
|
|
2116
|
+
ease: easeCubicOut
|
|
2117
|
+
}),
|
|
2118
|
+
resetView: ({ duration, silent } = {}) => {
|
|
2119
|
+
const length = duration ?? flightDuration({ from: current(), to: FIT_TRANSFORM, size });
|
|
2120
|
+
return animate(length, (t) => behavior.transform(t, zoomIdentity), {
|
|
2121
|
+
silent
|
|
2122
|
+
});
|
|
2123
|
+
},
|
|
2124
|
+
getView: () => toViewState({
|
|
2125
|
+
transform: current(),
|
|
2126
|
+
size,
|
|
2127
|
+
invert: latest.current.invert,
|
|
2128
|
+
level: ROOT_LEVEL
|
|
2129
|
+
})
|
|
2130
|
+
};
|
|
2131
|
+
store.registerZoom(api);
|
|
2132
|
+
return () => {
|
|
2133
|
+
const live = zoomTransform(svg);
|
|
2134
|
+
previous.current = {
|
|
2135
|
+
size,
|
|
2136
|
+
invert: latest.current.invert,
|
|
2137
|
+
camera: { k: live.k, x: live.x, y: live.y }
|
|
2138
|
+
};
|
|
2139
|
+
svg.removeEventListener("pointerdown", onPointerDown);
|
|
2140
|
+
if (glide !== null) cancelAnimationFrame(glide.frame);
|
|
2141
|
+
glide = null;
|
|
2142
|
+
fade?.cancel();
|
|
2143
|
+
store.registerZoom(null);
|
|
2144
|
+
selection.interrupt();
|
|
2145
|
+
selection.on(".zoom", null);
|
|
2146
|
+
svg.style.removeProperty("touch-action");
|
|
2147
|
+
root.toggleAttribute("data-zoomed", false);
|
|
2148
|
+
root.style.removeProperty("--cm-bubble-scale");
|
|
2149
|
+
store.setState({ interacting: false });
|
|
2150
|
+
};
|
|
2151
|
+
}, [
|
|
2152
|
+
svgRef,
|
|
2153
|
+
viewportRef,
|
|
2154
|
+
rootRef,
|
|
2155
|
+
store,
|
|
2156
|
+
enabled,
|
|
2157
|
+
ready,
|
|
2158
|
+
width,
|
|
2159
|
+
height,
|
|
2160
|
+
min,
|
|
2161
|
+
max,
|
|
2162
|
+
wheel,
|
|
2163
|
+
pinch,
|
|
2164
|
+
pan,
|
|
2165
|
+
doubleClick,
|
|
2166
|
+
constrain,
|
|
2167
|
+
bubbleScaling,
|
|
2168
|
+
inertia
|
|
2169
|
+
]);
|
|
2170
|
+
const applied = useRef6(UNSET);
|
|
2171
|
+
const focus = focusId ?? null;
|
|
2172
|
+
useEffect7(() => {
|
|
2173
|
+
if (!enabled || !ready) return;
|
|
2174
|
+
const api = store.getZoom();
|
|
2175
|
+
if (api === null) return;
|
|
2176
|
+
if (applied.current === UNSET) {
|
|
2177
|
+
applied.current = focus;
|
|
2178
|
+
const first = focus ?? initialView;
|
|
2179
|
+
if (first !== void 0 && first !== null)
|
|
2180
|
+
void api.zoomTo(first, { duration: 0, silent: true });
|
|
2181
|
+
return;
|
|
2182
|
+
}
|
|
2183
|
+
if (focus === applied.current) return;
|
|
2184
|
+
applied.current = focus;
|
|
2185
|
+
if (focus === null) void api.resetView();
|
|
2186
|
+
else void api.zoomTo(focus);
|
|
2187
|
+
}, [store, enabled, ready, focus]);
|
|
2188
|
+
useEffect7(() => () => store.dispose(), [store]);
|
|
2189
|
+
};
|
|
2190
|
+
var MAX_SAMPLES = 12;
|
|
2191
|
+
var GLIDE_STOP_PX = 0.5;
|
|
2192
|
+
var UNSET = /* @__PURE__ */ Symbol("unset");
|
|
2193
|
+
var NO_ZOOM = {
|
|
2194
|
+
min: 1,
|
|
2195
|
+
max: 1,
|
|
2196
|
+
wheel: "never",
|
|
2197
|
+
pinch: false,
|
|
2198
|
+
pan: false,
|
|
2199
|
+
doubleClick: false,
|
|
2200
|
+
constrain: "contain",
|
|
2201
|
+
bubbleScaling: "sqrt",
|
|
2202
|
+
inertia: false,
|
|
2203
|
+
keyboard: false,
|
|
2204
|
+
padding: 0,
|
|
2205
|
+
initialView: void 0
|
|
2206
|
+
};
|
|
2207
|
+
|
|
2208
|
+
// src/react/class-names.ts
|
|
2209
|
+
var cx = (...parts) => parts.filter((part) => typeof part === "string" && part !== "").join(" ");
|
|
2210
|
+
|
|
2211
|
+
// src/react/components/borders.tsx
|
|
2212
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
2213
|
+
var Borders = () => {
|
|
2214
|
+
const { choropleth, classNames } = useChoroplethContext();
|
|
2215
|
+
if (choropleth.borders === null) return null;
|
|
2216
|
+
return /* @__PURE__ */ jsx3(
|
|
2217
|
+
"path",
|
|
2218
|
+
{
|
|
2219
|
+
className: cx("cm-borders", classNames.borders),
|
|
2220
|
+
d: choropleth.borders,
|
|
2221
|
+
pointerEvents: "none"
|
|
2222
|
+
}
|
|
2223
|
+
);
|
|
2224
|
+
};
|
|
2225
|
+
|
|
2226
|
+
// src/react/components/breadcrumb.tsx
|
|
2227
|
+
import { Fragment, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
2228
|
+
var CORNERS = {
|
|
2229
|
+
"top-left": { top: 12, left: 12 },
|
|
2230
|
+
"top-right": { top: 12, right: 12 },
|
|
2231
|
+
"bottom-left": { bottom: 12, left: 12 },
|
|
2232
|
+
"bottom-right": { bottom: 12, right: 12 }
|
|
2233
|
+
};
|
|
2234
|
+
var Breadcrumb = ({ position, className, children }) => {
|
|
2235
|
+
const { choropleth, classNames } = useChoroplethContext();
|
|
2236
|
+
const { drillNav, labels } = choropleth;
|
|
2237
|
+
if (drillNav === null) return null;
|
|
2238
|
+
if (children !== void 0) return /* @__PURE__ */ jsx4(Fragment, { children: children(drillNav) });
|
|
2239
|
+
const { items, canDrillUp, drillTo, drillUp } = drillNav;
|
|
2240
|
+
return /* @__PURE__ */ jsxs2(
|
|
2241
|
+
"nav",
|
|
2242
|
+
{
|
|
2243
|
+
className: cx("cm-breadcrumb", classNames.breadcrumb, className),
|
|
2244
|
+
"aria-label": labels.breadcrumb,
|
|
2245
|
+
style: position === void 0 ? void 0 : { position: "absolute", ...CORNERS[position] },
|
|
2246
|
+
children: [
|
|
2247
|
+
canDrillUp && /* @__PURE__ */ jsx4(
|
|
2248
|
+
"button",
|
|
2249
|
+
{
|
|
2250
|
+
type: "button",
|
|
2251
|
+
className: "cm-breadcrumb__back",
|
|
2252
|
+
"aria-label": labels.drillUp,
|
|
2253
|
+
onClick: () => drillUp(),
|
|
2254
|
+
children: "\u2190"
|
|
2255
|
+
}
|
|
2256
|
+
),
|
|
2257
|
+
/* @__PURE__ */ jsx4("ol", { className: "cm-breadcrumb__list", children: items.map((item) => /* @__PURE__ */ jsx4("li", { className: cx("cm-breadcrumb__item", classNames.breadcrumbItem), children: item.current ? /* @__PURE__ */ jsx4("span", { className: "cm-breadcrumb__current", "aria-current": "page", children: item.label }) : /* @__PURE__ */ jsx4(
|
|
2258
|
+
"button",
|
|
2259
|
+
{
|
|
2260
|
+
type: "button",
|
|
2261
|
+
className: "cm-breadcrumb__link",
|
|
2262
|
+
onClick: () => drillTo(item.depth),
|
|
2263
|
+
children: item.label
|
|
2264
|
+
}
|
|
2265
|
+
) }, item.depth)) })
|
|
2266
|
+
]
|
|
2267
|
+
}
|
|
2268
|
+
);
|
|
2269
|
+
};
|
|
2270
|
+
|
|
2271
|
+
// src/react/components/bubbles.tsx
|
|
2272
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
2273
|
+
var Bubbles = () => {
|
|
2274
|
+
const { choropleth, classNames } = useChoroplethContext();
|
|
2275
|
+
const { bubbles, clickable, getPointProps } = choropleth;
|
|
2276
|
+
if (bubbles.length === 0) return null;
|
|
2277
|
+
return /* @__PURE__ */ jsx5("g", { className: cx("cm-bubbles", classNames.bubbles), pointerEvents: "none", children: bubbles.map((bubble) => {
|
|
2278
|
+
const isPoint = bubble.coordinates !== null;
|
|
2279
|
+
return /* @__PURE__ */ jsx5(
|
|
2280
|
+
"circle",
|
|
2281
|
+
{
|
|
2282
|
+
className: cx("cm-bubble", classNames.bubble),
|
|
2283
|
+
"data-bubble-id": bubble.id,
|
|
2284
|
+
cx: bubble.x,
|
|
2285
|
+
cy: bubble.y,
|
|
2286
|
+
r: bubble.r,
|
|
2287
|
+
style: { fill: bubble.fill, cursor: isPoint && clickable ? "pointer" : void 0 },
|
|
2288
|
+
pointerEvents: isPoint ? "auto" : void 0,
|
|
2289
|
+
...isPoint ? getPointProps(bubble) : void 0
|
|
2290
|
+
},
|
|
2291
|
+
bubble.id
|
|
2292
|
+
);
|
|
2293
|
+
}) });
|
|
2294
|
+
};
|
|
2295
|
+
|
|
2296
|
+
// src/react/components/drill-layers.tsx
|
|
2297
|
+
import { Fragment as Fragment2, jsx as jsx6 } from "react/jsx-runtime";
|
|
2298
|
+
var DrillLayers = ({ role }) => {
|
|
2299
|
+
const { choropleth, classNames } = useChoroplethContext();
|
|
2300
|
+
const { drillLayers, selectContextItem } = choropleth;
|
|
2301
|
+
const layers = drillLayers.filter((layer) => layer.role === role);
|
|
2302
|
+
if (layers.length === 0) return null;
|
|
2303
|
+
return /* @__PURE__ */ jsx6(Fragment2, { children: layers.map((layer) => {
|
|
2304
|
+
const { k, x, y } = layer.transform;
|
|
2305
|
+
return /* @__PURE__ */ jsx6(
|
|
2306
|
+
"g",
|
|
2307
|
+
{
|
|
2308
|
+
className: cx("cm-layer", classNames.layer),
|
|
2309
|
+
"data-role": layer.role,
|
|
2310
|
+
"data-depth": layer.depth,
|
|
2311
|
+
transform: `translate(${x},${y}) scale(${k})`,
|
|
2312
|
+
pointerEvents: layer.role === "exiting" ? "none" : void 0,
|
|
2313
|
+
onClick: layer.role === "context" ? (event) => {
|
|
2314
|
+
const id = event.target.closest?.("[data-context-id]")?.getAttribute("data-context-id");
|
|
2315
|
+
const known = id == null ? void 0 : layer.paths.find((p) => p.id === id);
|
|
2316
|
+
if (known?.drillable === true && id != null) selectContextItem(layer.depth, id);
|
|
2317
|
+
} : void 0,
|
|
2318
|
+
children: layer.paths.map((path, index) => /* @__PURE__ */ jsx6(
|
|
2319
|
+
"path",
|
|
2320
|
+
{
|
|
2321
|
+
className: "cm-context-region",
|
|
2322
|
+
d: path.d,
|
|
2323
|
+
"data-context-id": path.id,
|
|
2324
|
+
"data-focus": path.focus ? "" : void 0,
|
|
2325
|
+
"data-drillable": path.drillable ? "" : void 0,
|
|
2326
|
+
style: path.fill === void 0 ? void 0 : { fill: path.fill }
|
|
2327
|
+
},
|
|
2328
|
+
path.id ?? `anonymous-${index}`
|
|
2329
|
+
))
|
|
2330
|
+
},
|
|
2331
|
+
layer.key
|
|
2332
|
+
);
|
|
2333
|
+
}) });
|
|
2334
|
+
};
|
|
2335
|
+
|
|
2336
|
+
// src/react/components/fallback-points.tsx
|
|
2337
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
2338
|
+
var FallbackPoints = () => {
|
|
2339
|
+
const { choropleth } = useChoroplethContext();
|
|
2340
|
+
const { fallbackPoints, clickable, getPointProps } = choropleth;
|
|
2341
|
+
if (fallbackPoints.length === 0) return null;
|
|
2342
|
+
return /* @__PURE__ */ jsx7("g", { className: "cm-fallback-points", children: fallbackPoints.map((point) => /* @__PURE__ */ jsx7(
|
|
2343
|
+
"circle",
|
|
2344
|
+
{
|
|
2345
|
+
className: "cm-fallback-point",
|
|
2346
|
+
cx: point.x,
|
|
2347
|
+
cy: point.y,
|
|
2348
|
+
r: point.r,
|
|
2349
|
+
style: { fill: point.fill, cursor: clickable ? "pointer" : void 0 },
|
|
2350
|
+
...getPointProps(point)
|
|
2351
|
+
},
|
|
2352
|
+
point.id
|
|
2353
|
+
)) });
|
|
2354
|
+
};
|
|
2355
|
+
|
|
2356
|
+
// src/react/components/gesture-hint.tsx
|
|
2357
|
+
import { useState as useState6 } from "react";
|
|
2358
|
+
|
|
2359
|
+
// src/react/platform.ts
|
|
2360
|
+
import { useSyncExternalStore as useSyncExternalStore4 } from "react";
|
|
2361
|
+
var isApplePlatform = () => {
|
|
2362
|
+
if (typeof navigator === "undefined") return false;
|
|
2363
|
+
const platform = navigator.userAgentData?.platform ?? navigator.platform ?? "";
|
|
2364
|
+
return /Mac|iPhone|iPad|iPod/i.test(platform);
|
|
2365
|
+
};
|
|
2366
|
+
var subscribeNothing = () => () => {
|
|
2367
|
+
};
|
|
2368
|
+
var useIsApplePlatform = () => useSyncExternalStore4(subscribeNothing, isApplePlatform, () => false);
|
|
2369
|
+
|
|
2370
|
+
// src/react/components/gesture-hint.tsx
|
|
2371
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
2372
|
+
var GestureHint = ({ className }) => {
|
|
2373
|
+
const { choropleth, store, classNames } = useChoroplethContext();
|
|
2374
|
+
const hint = useMapStore(store, (s) => s.gestureHint);
|
|
2375
|
+
const apple = useIsApplePlatform();
|
|
2376
|
+
const [last, setLast] = useState6("wheel");
|
|
2377
|
+
if (hint !== null && hint !== last) setLast(hint);
|
|
2378
|
+
const { labels } = choropleth;
|
|
2379
|
+
const text = last === "touch" ? labels.gestureHintTouch : apple ? labels.gestureHintWheelMac : labels.gestureHintWheel;
|
|
2380
|
+
return /* @__PURE__ */ jsx8(
|
|
2381
|
+
"div",
|
|
2382
|
+
{
|
|
2383
|
+
className: cx("cm-gesture-hint", classNames.gestureHint, className),
|
|
2384
|
+
"aria-hidden": "true",
|
|
2385
|
+
"data-visible": hint === null ? void 0 : "",
|
|
2386
|
+
children: text
|
|
2387
|
+
}
|
|
2388
|
+
);
|
|
2389
|
+
};
|
|
2390
|
+
|
|
2391
|
+
// src/react/components/legend.tsx
|
|
2392
|
+
import { useEffect as useEffect8 } from "react";
|
|
2393
|
+
import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
2394
|
+
var GRADIENT_STOPS = 5;
|
|
2395
|
+
var Legend = ({
|
|
2396
|
+
title,
|
|
2397
|
+
showCount = false,
|
|
2398
|
+
className,
|
|
2399
|
+
variant = "swatches",
|
|
2400
|
+
interactive = true,
|
|
2401
|
+
children
|
|
2402
|
+
}) => {
|
|
2403
|
+
const { choropleth, classNames, emptyPattern } = useChoroplethContext();
|
|
2404
|
+
const { legend: items, sizeLegend: sizeItems, scale, formatValue, containerRef } = choropleth;
|
|
2405
|
+
const canHighlight = interactive && choropleth.regions.some((r) => r.classIndex !== null);
|
|
2406
|
+
const highlight = (key) => {
|
|
2407
|
+
const root = containerRef.current;
|
|
2408
|
+
if (root === null) return;
|
|
2409
|
+
if (key === null) root.removeAttribute("data-highlight-class");
|
|
2410
|
+
else root.setAttribute("data-highlight-class", key);
|
|
2411
|
+
};
|
|
2412
|
+
useEffect8(
|
|
2413
|
+
() => () => containerRef.current?.removeAttribute("data-highlight-class"),
|
|
2414
|
+
[containerRef]
|
|
2415
|
+
);
|
|
2416
|
+
const classKey = (item, index) => item.empty === true ? "empty" : String(index);
|
|
2417
|
+
const hoverProps = (item, index) => canHighlight ? {
|
|
2418
|
+
"data-class": classKey(item, index),
|
|
2419
|
+
onPointerEnter: () => highlight(classKey(item, index)),
|
|
2420
|
+
onPointerLeave: () => highlight(null)
|
|
2421
|
+
} : {};
|
|
2422
|
+
const swatchProps = (item) => ({
|
|
2423
|
+
style: { backgroundColor: item.color },
|
|
2424
|
+
"data-pattern": item.empty === true && emptyPattern !== null ? emptyPattern : void 0
|
|
2425
|
+
});
|
|
2426
|
+
if (children !== void 0) return /* @__PURE__ */ jsx9(Fragment3, { children: children({ items, sizeItems, scale, formatValue }) });
|
|
2427
|
+
const rootClass = cx("cm-legend", classNames.legend, className);
|
|
2428
|
+
const indexed = items.map((item, index) => ({ item, index }));
|
|
2429
|
+
const classRows = indexed.filter(({ item }) => item.empty !== true);
|
|
2430
|
+
const emptyItem = indexed.find(({ item }) => item.empty === true) ?? null;
|
|
2431
|
+
const barItems = variant === "bar" && scale?.kind !== "continuous" && classRows.length > 0 && classRows.every(({ item }) => item.from !== void 0 && item.to !== void 0) ? classRows : null;
|
|
2432
|
+
const barTicks = barItems === null ? [] : [...barItems.map(({ item }) => item.from), barItems.at(-1)?.item.to];
|
|
2433
|
+
const [min, max] = scale?.domain ?? [0, 1];
|
|
2434
|
+
return /* @__PURE__ */ jsxs3("div", { className: rootClass, children: [
|
|
2435
|
+
title !== void 0 && /* @__PURE__ */ jsx9("div", { className: "cm-legend__title", children: title }),
|
|
2436
|
+
scale !== null && scale.kind === "continuous" ? /* @__PURE__ */ jsxs3("div", { children: [
|
|
2437
|
+
/* @__PURE__ */ jsx9(
|
|
2438
|
+
"div",
|
|
2439
|
+
{
|
|
2440
|
+
className: "cm-legend__gradient",
|
|
2441
|
+
"data-legend-gradient": true,
|
|
2442
|
+
style: {
|
|
2443
|
+
background: `linear-gradient(to right, ${Array.from(
|
|
2444
|
+
{ length: GRADIENT_STOPS },
|
|
2445
|
+
(_, i) => scale.getColor(min + (max - min) * i / (GRADIENT_STOPS - 1))
|
|
2446
|
+
).join(", ")})`
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
),
|
|
2450
|
+
/* @__PURE__ */ jsxs3("div", { className: "cm-legend__range", children: [
|
|
2451
|
+
/* @__PURE__ */ jsx9("span", { children: formatValue(min) }),
|
|
2452
|
+
/* @__PURE__ */ jsx9("span", { children: formatValue(max) })
|
|
2453
|
+
] })
|
|
2454
|
+
] }) : barItems !== null ? /* @__PURE__ */ jsxs3("div", { className: "cm-legend__bar-wrap", children: [
|
|
2455
|
+
/* @__PURE__ */ jsx9("div", { className: "cm-legend__bar", role: "list", children: barItems.map(({ item, index }) => /* @__PURE__ */ jsx9(
|
|
2456
|
+
"span",
|
|
2457
|
+
{
|
|
2458
|
+
role: "listitem",
|
|
2459
|
+
className: cx("cm-legend__segment", classNames.legendItem),
|
|
2460
|
+
style: { backgroundColor: item.color },
|
|
2461
|
+
title: `${item.label} (${item.count})`,
|
|
2462
|
+
...hoverProps(item, index)
|
|
2463
|
+
},
|
|
2464
|
+
item.label
|
|
2465
|
+
)) }),
|
|
2466
|
+
/* @__PURE__ */ jsx9("div", { className: "cm-legend__ticks", "aria-hidden": "true", children: barTicks.map((value, i) => /* @__PURE__ */ jsx9(
|
|
2467
|
+
"span",
|
|
2468
|
+
{
|
|
2469
|
+
className: "cm-legend__tick",
|
|
2470
|
+
style: { left: `${i / (barTicks.length - 1) * 100}%` },
|
|
2471
|
+
children: formatValue(value)
|
|
2472
|
+
},
|
|
2473
|
+
i
|
|
2474
|
+
)) }),
|
|
2475
|
+
emptyItem !== null && /* @__PURE__ */ jsxs3(
|
|
2476
|
+
"div",
|
|
2477
|
+
{
|
|
2478
|
+
className: cx("cm-legend__item", classNames.legendItem),
|
|
2479
|
+
...hoverProps(emptyItem.item, emptyItem.index),
|
|
2480
|
+
children: [
|
|
2481
|
+
/* @__PURE__ */ jsx9("span", { "aria-hidden": true, className: "cm-legend__swatch", ...swatchProps(emptyItem.item) }),
|
|
2482
|
+
/* @__PURE__ */ jsx9("span", { children: emptyItem.item.label }),
|
|
2483
|
+
showCount && /* @__PURE__ */ jsxs3("span", { className: "cm-legend__count", children: [
|
|
2484
|
+
"(",
|
|
2485
|
+
emptyItem.item.count,
|
|
2486
|
+
")"
|
|
2487
|
+
] })
|
|
2488
|
+
]
|
|
2489
|
+
}
|
|
2490
|
+
)
|
|
2491
|
+
] }) : items.length > 0 && /* @__PURE__ */ jsx9("ul", { className: "cm-legend__items", children: items.map((item, index) => /* @__PURE__ */ jsxs3(
|
|
2492
|
+
"li",
|
|
2493
|
+
{
|
|
2494
|
+
className: cx("cm-legend__item", classNames.legendItem),
|
|
2495
|
+
...hoverProps(item, index),
|
|
2496
|
+
children: [
|
|
2497
|
+
/* @__PURE__ */ jsx9("span", { "aria-hidden": true, className: "cm-legend__swatch", ...swatchProps(item) }),
|
|
2498
|
+
/* @__PURE__ */ jsx9("span", { children: item.label }),
|
|
2499
|
+
showCount && /* @__PURE__ */ jsxs3("span", { className: "cm-legend__count", children: [
|
|
2500
|
+
"(",
|
|
2501
|
+
item.count,
|
|
2502
|
+
")"
|
|
2503
|
+
] })
|
|
2504
|
+
]
|
|
2505
|
+
},
|
|
2506
|
+
item.label
|
|
2507
|
+
)) }),
|
|
2508
|
+
sizeItems.length > 0 && /* @__PURE__ */ jsx9("ul", { className: "cm-legend__sizes", children: sizeItems.map((item) => /* @__PURE__ */ jsxs3("li", { className: "cm-legend__size", children: [
|
|
2509
|
+
/* @__PURE__ */ jsx9(
|
|
2510
|
+
"svg",
|
|
2511
|
+
{
|
|
2512
|
+
"aria-hidden": true,
|
|
2513
|
+
width: item.radius * 2 + 2,
|
|
2514
|
+
height: item.radius * 2 + 2,
|
|
2515
|
+
className: "cm-legend__circle",
|
|
2516
|
+
children: /* @__PURE__ */ jsx9("circle", { cx: item.radius + 1, cy: item.radius + 1, r: item.radius })
|
|
2517
|
+
}
|
|
2518
|
+
),
|
|
2519
|
+
/* @__PURE__ */ jsx9("span", { children: item.label })
|
|
2520
|
+
] }, item.label)) })
|
|
2521
|
+
] });
|
|
2522
|
+
};
|
|
2523
|
+
|
|
2524
|
+
// src/react/components/regions.tsx
|
|
2525
|
+
import { memo } from "react";
|
|
2526
|
+
import { jsx as jsx10, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
2527
|
+
var RegionPath = memo(
|
|
2528
|
+
({
|
|
2529
|
+
id,
|
|
2530
|
+
d,
|
|
2531
|
+
fill,
|
|
2532
|
+
drillable,
|
|
2533
|
+
empty,
|
|
2534
|
+
dataClass,
|
|
2535
|
+
className,
|
|
2536
|
+
opacity,
|
|
2537
|
+
target,
|
|
2538
|
+
wave
|
|
2539
|
+
}) => /* @__PURE__ */ jsx10(
|
|
2540
|
+
"path",
|
|
2541
|
+
{
|
|
2542
|
+
className: cx("cm-region", className),
|
|
2543
|
+
"data-region-id": id,
|
|
2544
|
+
"data-empty": empty ? "" : void 0,
|
|
2545
|
+
"data-class": dataClass,
|
|
2546
|
+
"data-drillable": drillable ? "" : void 0,
|
|
2547
|
+
"data-drill-target": target ? "" : void 0,
|
|
2548
|
+
d,
|
|
2549
|
+
style: {
|
|
2550
|
+
fill,
|
|
2551
|
+
...opacity === void 0 ? void 0 : { opacity },
|
|
2552
|
+
...wave === void 0 ? void 0 : { "--cm-wave": wave }
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
)
|
|
2556
|
+
);
|
|
2557
|
+
var AnonymousPath = memo(({ d, fill }) => /* @__PURE__ */ jsx10("path", { className: "cm-region cm-region--anonymous", d, style: { fill, cursor: "default" } }));
|
|
2558
|
+
var regionIdFrom = (event) => event.target.closest?.("[data-region-id]")?.getAttribute("data-region-id") ?? null;
|
|
2559
|
+
var Regions = () => {
|
|
2560
|
+
const { choropleth, store, classNames } = useChoroplethContext();
|
|
2561
|
+
const {
|
|
2562
|
+
regions,
|
|
2563
|
+
anonymousPaths,
|
|
2564
|
+
emptyFill,
|
|
2565
|
+
borders,
|
|
2566
|
+
clickable,
|
|
2567
|
+
hoverItem,
|
|
2568
|
+
clickItem,
|
|
2569
|
+
drillFocusId
|
|
2570
|
+
} = choropleth;
|
|
2571
|
+
const known = new Set(regions.map((region) => region.id));
|
|
2572
|
+
const regionRef = (event) => {
|
|
2573
|
+
const id = regionIdFrom(event);
|
|
2574
|
+
return id !== null && known.has(id) ? { kind: "region", id } : null;
|
|
2575
|
+
};
|
|
2576
|
+
const outlined = borders === null;
|
|
2577
|
+
return /* @__PURE__ */ jsxs4(
|
|
2578
|
+
"g",
|
|
2579
|
+
{
|
|
2580
|
+
className: cx("cm-regions", classNames.regions),
|
|
2581
|
+
"data-outlined": outlined ? "" : void 0,
|
|
2582
|
+
style: clickable ? { cursor: "pointer" } : void 0,
|
|
2583
|
+
onClick: (event) => {
|
|
2584
|
+
const ref = regionRef(event);
|
|
2585
|
+
if (ref !== null) clickItem(ref);
|
|
2586
|
+
},
|
|
2587
|
+
onPointerOver: (event) => {
|
|
2588
|
+
const ref = regionRef(event);
|
|
2589
|
+
if (ref !== null) {
|
|
2590
|
+
hoverItem(ref);
|
|
2591
|
+
} else if (store.getState().hovered?.kind === "region") {
|
|
2592
|
+
hoverItem(null);
|
|
2593
|
+
}
|
|
2594
|
+
},
|
|
2595
|
+
onPointerLeave: (event) => {
|
|
2596
|
+
if (event.pointerType === "touch") return;
|
|
2597
|
+
if (store.getState().hovered?.kind === "region") hoverItem(null);
|
|
2598
|
+
},
|
|
2599
|
+
children: [
|
|
2600
|
+
anonymousPaths.map((d, index) => /* @__PURE__ */ jsx10(AnonymousPath, { d, fill: emptyFill }, `anonymous-${index}`)),
|
|
2601
|
+
regions.map((region) => /* @__PURE__ */ jsx10(
|
|
2602
|
+
RegionPath,
|
|
2603
|
+
{
|
|
2604
|
+
id: region.id,
|
|
2605
|
+
d: region.path,
|
|
2606
|
+
fill: region.fill,
|
|
2607
|
+
drillable: region.drillable,
|
|
2608
|
+
empty: region.value === null,
|
|
2609
|
+
dataClass: region.classIndex === null ? "empty" : String(region.classIndex),
|
|
2610
|
+
className: cx(classNames.region, region.className),
|
|
2611
|
+
opacity: region.opacity,
|
|
2612
|
+
target: region.id === drillFocusId,
|
|
2613
|
+
wave: region.wave
|
|
2614
|
+
},
|
|
2615
|
+
region.id
|
|
2616
|
+
))
|
|
2617
|
+
]
|
|
2618
|
+
}
|
|
2619
|
+
);
|
|
2620
|
+
};
|
|
2621
|
+
|
|
2622
|
+
// src/react/components/root.tsx
|
|
2623
|
+
import {
|
|
2624
|
+
forwardRef,
|
|
2625
|
+
useCallback as useCallback3,
|
|
2626
|
+
useEffect as useEffect9,
|
|
2627
|
+
useId,
|
|
2628
|
+
useImperativeHandle,
|
|
2629
|
+
useMemo as useMemo6,
|
|
2630
|
+
useRef as useRef7
|
|
2631
|
+
} from "react";
|
|
2632
|
+
|
|
2633
|
+
// src/react/handle.ts
|
|
2634
|
+
var EMPTY_VIEW = {
|
|
2635
|
+
k: 1,
|
|
2636
|
+
x: 0,
|
|
2637
|
+
y: 0,
|
|
2638
|
+
zoom: 1,
|
|
2639
|
+
center: [0, 0],
|
|
2640
|
+
bounds: [
|
|
2641
|
+
[-180, -90],
|
|
2642
|
+
[180, 90]
|
|
2643
|
+
],
|
|
2644
|
+
level: { id: "root", depth: 0 }
|
|
2645
|
+
};
|
|
2646
|
+
var NOT_AVAILABLE = Promise.resolve({ completed: false });
|
|
2647
|
+
var noDrill = (method) => {
|
|
2648
|
+
if (!isProduction()) {
|
|
2649
|
+
console.warn(`[choropleth] ${method}() needs the \`drill\` prop; this map has none.`);
|
|
2650
|
+
}
|
|
2651
|
+
return NOT_AVAILABLE;
|
|
2652
|
+
};
|
|
2653
|
+
var createHandle = (store) => ({
|
|
2654
|
+
zoomTo: (target, options) => store.getZoom()?.zoomTo(target, options) ?? NOT_AVAILABLE,
|
|
2655
|
+
zoomBy: (factor, options) => store.getZoom()?.zoomBy(factor, options) ?? NOT_AVAILABLE,
|
|
2656
|
+
resetView: (options) => store.getZoom()?.resetView(options) ?? NOT_AVAILABLE,
|
|
2657
|
+
getView: () => store.getZoom()?.getView() ?? store.getFallbackView() ?? EMPTY_VIEW,
|
|
2658
|
+
drillDown: (id) => store.getDrill()?.drillDown(id) ?? noDrill("drillDown"),
|
|
2659
|
+
drillUp: (steps) => store.getDrill()?.drillUp(steps) ?? noDrill("drillUp"),
|
|
2660
|
+
drillTo: (path) => store.getDrill()?.drillTo(path) ?? noDrill("drillTo")
|
|
2661
|
+
});
|
|
2662
|
+
|
|
2663
|
+
// src/react/components/root.tsx
|
|
2664
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
2665
|
+
var RootInner = ({
|
|
2666
|
+
children,
|
|
2667
|
+
className,
|
|
2668
|
+
style,
|
|
2669
|
+
unstyled,
|
|
2670
|
+
theme,
|
|
2671
|
+
emptyPattern,
|
|
2672
|
+
classNames,
|
|
2673
|
+
dimOthers = "selection",
|
|
2674
|
+
...options
|
|
2675
|
+
}, ref) => {
|
|
2676
|
+
const choropleth = useChoropleth({
|
|
2677
|
+
...options,
|
|
2678
|
+
dimOthers
|
|
2679
|
+
});
|
|
2680
|
+
const { containerRef, store, status, dataLoading, selectedId, drillPhase } = choropleth;
|
|
2681
|
+
const viewportRef = useRef7(null);
|
|
2682
|
+
useEffect9(() => {
|
|
2683
|
+
const sync = () => {
|
|
2684
|
+
containerRef.current?.toggleAttribute("data-interacting", store.getState().interacting);
|
|
2685
|
+
};
|
|
2686
|
+
sync();
|
|
2687
|
+
return store.subscribe(sync);
|
|
2688
|
+
}, [store, containerRef]);
|
|
2689
|
+
const { width, height, invert } = choropleth;
|
|
2690
|
+
useEffect9(() => {
|
|
2691
|
+
store.registerFallbackView(
|
|
2692
|
+
() => toViewState({ transform: FIT_TRANSFORM, size: { width, height }, invert, level: ROOT_LEVEL })
|
|
2693
|
+
);
|
|
2694
|
+
return () => store.registerFallbackView(null);
|
|
2695
|
+
}, [store, width, height, invert]);
|
|
2696
|
+
const api = useMemo6(() => createHandle(store), [store]);
|
|
2697
|
+
useImperativeHandle(ref, () => api, [api]);
|
|
2698
|
+
const themeVars = useMemo6(() => themeToCssVars(resolveTheme(theme)), [theme]);
|
|
2699
|
+
const slots = useMemo6(() => classNames ?? {}, [classNames]);
|
|
2700
|
+
const instance = useId().replace(/[^a-zA-Z0-9_-]/g, "");
|
|
2701
|
+
const defId = useCallback3((name) => `cm${instance}-${name}`, [instance]);
|
|
2702
|
+
const pattern = emptyPattern ?? resolveTheme(theme)?.empty?.pattern;
|
|
2703
|
+
const resolvedPattern = pattern === "hatch" || pattern === "dots" ? pattern : null;
|
|
2704
|
+
const handlePointer = (event) => {
|
|
2705
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
2706
|
+
store.setState({
|
|
2707
|
+
pointer: { x: event.clientX - rect.left, y: event.clientY - rect.top },
|
|
2708
|
+
// The drill asks for one or two taps depending on what is touching (a pen's wheel counts as a mouse).
|
|
2709
|
+
pointerType: event.pointerType === "touch" ? "touch" : event.pointerType === "pen" ? "pen" : "mouse"
|
|
2710
|
+
});
|
|
2711
|
+
};
|
|
2712
|
+
return /* @__PURE__ */ jsx11(
|
|
2713
|
+
ChoroplethContext.Provider,
|
|
2714
|
+
{
|
|
2715
|
+
value: {
|
|
2716
|
+
choropleth,
|
|
2717
|
+
store,
|
|
2718
|
+
viewportRef,
|
|
2719
|
+
api,
|
|
2720
|
+
classNames: slots,
|
|
2721
|
+
defId,
|
|
2722
|
+
emptyPattern: resolvedPattern
|
|
2723
|
+
},
|
|
2724
|
+
children: /* @__PURE__ */ jsx11(
|
|
2725
|
+
"div",
|
|
2726
|
+
{
|
|
2727
|
+
ref: containerRef,
|
|
2728
|
+
className: cx("cm-root", slots.root, className),
|
|
2729
|
+
"data-unstyled": unstyled ? "" : void 0,
|
|
2730
|
+
"data-theme": theme === void 0 ? void 0 : isThemeName(theme) ? theme : "custom",
|
|
2731
|
+
"data-status": status,
|
|
2732
|
+
"data-loading": dataLoading ? "" : void 0,
|
|
2733
|
+
"data-dim": dimOthers,
|
|
2734
|
+
"data-drill-phase": drillPhase === null || drillPhase === "idle" ? void 0 : drillPhase,
|
|
2735
|
+
"data-has-selection": selectedId === null ? void 0 : "",
|
|
2736
|
+
style: { position: "relative", ...themeVars, ...style },
|
|
2737
|
+
onPointerMove: handlePointer,
|
|
2738
|
+
onPointerDown: handlePointer,
|
|
2739
|
+
onPointerLeave: (event) => {
|
|
2740
|
+
if (event.pointerType !== "touch") store.setState({ pointer: null });
|
|
2741
|
+
},
|
|
2742
|
+
children
|
|
2743
|
+
}
|
|
2744
|
+
)
|
|
2745
|
+
}
|
|
2746
|
+
);
|
|
2747
|
+
};
|
|
2748
|
+
var Root = forwardRef(RootInner);
|
|
2749
|
+
|
|
2750
|
+
// src/react/components/selection.tsx
|
|
2751
|
+
import { jsx as jsx12, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
2752
|
+
var Selection = () => {
|
|
2753
|
+
const { choropleth, store, classNames } = useChoroplethContext();
|
|
2754
|
+
const { regions, bubbles, fallbackPoints, selectedId } = choropleth;
|
|
2755
|
+
const hovered = useMapStore(store, (s) => s.hovered);
|
|
2756
|
+
const hoveredRegion = hovered?.kind === "region" ? regions.find((r) => r.id === hovered.id) : void 0;
|
|
2757
|
+
const hoveredPoint = hovered?.kind === "point" ? bubbles.find((b) => b.coordinates !== null && b.id === hovered.id) ?? fallbackPoints.find((p) => p.id === hovered.id) : void 0;
|
|
2758
|
+
const selectedRegion = selectedId === null ? void 0 : regions.find((r) => r.id === selectedId);
|
|
2759
|
+
return /* @__PURE__ */ jsxs5("g", { className: cx("cm-selection", classNames.selection), pointerEvents: "none", children: [
|
|
2760
|
+
hoveredRegion !== void 0 && /* @__PURE__ */ jsx12("path", { "data-selection": "hover", d: hoveredRegion.path, style: { fill: hoveredRegion.fill } }),
|
|
2761
|
+
hoveredPoint !== void 0 && /* @__PURE__ */ jsx12("circle", { "data-selection": "hover", cx: hoveredPoint.x, cy: hoveredPoint.y, r: hoveredPoint.r }),
|
|
2762
|
+
selectedRegion !== void 0 && /* @__PURE__ */ jsx12(
|
|
2763
|
+
"path",
|
|
2764
|
+
{
|
|
2765
|
+
"data-selection": "selected",
|
|
2766
|
+
d: selectedRegion.path,
|
|
2767
|
+
style: { fill: selectedRegion.fill }
|
|
2768
|
+
}
|
|
2769
|
+
)
|
|
2770
|
+
] });
|
|
2771
|
+
};
|
|
2772
|
+
|
|
2773
|
+
// src/react/components/svg.tsx
|
|
2774
|
+
import { useRef as useRef8 } from "react";
|
|
2775
|
+
|
|
2776
|
+
// src/react/components/viewport.tsx
|
|
2777
|
+
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
2778
|
+
var Viewport = ({ children }) => {
|
|
2779
|
+
const { viewportRef } = useChoroplethContext();
|
|
2780
|
+
return /* @__PURE__ */ jsx13("g", { ref: viewportRef, className: "cm-viewport", children });
|
|
2781
|
+
};
|
|
2782
|
+
|
|
2783
|
+
// src/react/components/svg.tsx
|
|
2784
|
+
import { Fragment as Fragment4, jsx as jsx14, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2785
|
+
var Svg = ({ children, className, ariaLabel }) => {
|
|
2786
|
+
const { choropleth, store, viewportRef, classNames } = useChoroplethContext();
|
|
2787
|
+
const {
|
|
2788
|
+
width,
|
|
2789
|
+
height,
|
|
2790
|
+
status,
|
|
2791
|
+
labels,
|
|
2792
|
+
zoom: zoom2,
|
|
2793
|
+
invert,
|
|
2794
|
+
project,
|
|
2795
|
+
onViewChange,
|
|
2796
|
+
resolveCamera,
|
|
2797
|
+
focusId,
|
|
2798
|
+
containerRef
|
|
2799
|
+
} = choropleth;
|
|
2800
|
+
const svgRef = useRef8(null);
|
|
2801
|
+
useZoom({
|
|
2802
|
+
svgRef,
|
|
2803
|
+
viewportRef,
|
|
2804
|
+
rootRef: containerRef,
|
|
2805
|
+
store,
|
|
2806
|
+
width,
|
|
2807
|
+
height,
|
|
2808
|
+
options: zoom2,
|
|
2809
|
+
invert,
|
|
2810
|
+
project,
|
|
2811
|
+
onViewChange,
|
|
2812
|
+
resolveCamera,
|
|
2813
|
+
focusId,
|
|
2814
|
+
ready: status === "ready"
|
|
2815
|
+
});
|
|
2816
|
+
if (status === "loading") {
|
|
2817
|
+
return /* @__PURE__ */ jsx14("div", { className: "cm-skeleton", style: { aspectRatio: `${width} / ${height}` } });
|
|
2818
|
+
}
|
|
2819
|
+
if (status === "error") {
|
|
2820
|
+
return /* @__PURE__ */ jsx14("div", { className: "cm-error", role: "alert", style: { aspectRatio: `${width} / ${height}` }, children: labels.geographyError });
|
|
2821
|
+
}
|
|
2822
|
+
const { drillNav } = choropleth;
|
|
2823
|
+
const keyboard = zoom2 !== null && zoom2.keyboard || drillNav !== null;
|
|
2824
|
+
const handleKeyDown = (event) => {
|
|
2825
|
+
if (drillNav?.canDrillUp === true && (event.key === "Escape" || event.key === "Backspace") && !event.ctrlKey && !event.metaKey && !event.altKey) {
|
|
2826
|
+
event.preventDefault();
|
|
2827
|
+
drillNav.drillUp();
|
|
2828
|
+
return;
|
|
2829
|
+
}
|
|
2830
|
+
if (zoom2 === null || !zoom2.keyboard) return;
|
|
2831
|
+
const action = zoomKeyAction(event);
|
|
2832
|
+
if (action === null) return;
|
|
2833
|
+
event.preventDefault();
|
|
2834
|
+
const api = store.getZoom();
|
|
2835
|
+
if (action === "reset") void api?.resetView();
|
|
2836
|
+
else void api?.zoomBy(action === "in" ? 2 : 0.5);
|
|
2837
|
+
};
|
|
2838
|
+
return /* @__PURE__ */ jsx14(
|
|
2839
|
+
"svg",
|
|
2840
|
+
{
|
|
2841
|
+
ref: svgRef,
|
|
2842
|
+
tabIndex: keyboard ? 0 : void 0,
|
|
2843
|
+
onKeyDown: keyboard ? handleKeyDown : void 0,
|
|
2844
|
+
className: cx("cm-svg", classNames.svg, className),
|
|
2845
|
+
viewBox: `0 0 ${width} ${height}`,
|
|
2846
|
+
role: "img",
|
|
2847
|
+
"aria-label": ariaLabel,
|
|
2848
|
+
style: { display: "block", width: "100%", height: "auto" },
|
|
2849
|
+
children: children ?? /* @__PURE__ */ jsxs6(Fragment4, { children: [
|
|
2850
|
+
/* @__PURE__ */ jsx14(Defs, {}),
|
|
2851
|
+
/* @__PURE__ */ jsxs6(Viewport, { children: [
|
|
2852
|
+
/* @__PURE__ */ jsx14(DrillLayers, { role: "context" }),
|
|
2853
|
+
/* @__PURE__ */ jsx14(Regions, {}),
|
|
2854
|
+
/* @__PURE__ */ jsx14(Patterns, {}),
|
|
2855
|
+
/* @__PURE__ */ jsx14(Borders, {}),
|
|
2856
|
+
/* @__PURE__ */ jsx14(Bubbles, {}),
|
|
2857
|
+
/* @__PURE__ */ jsx14(FallbackPoints, {}),
|
|
2858
|
+
/* @__PURE__ */ jsx14(Selection, {}),
|
|
2859
|
+
/* @__PURE__ */ jsx14(DrillLayers, { role: "exiting" })
|
|
2860
|
+
] })
|
|
2861
|
+
] })
|
|
2862
|
+
}
|
|
2863
|
+
);
|
|
2864
|
+
};
|
|
2865
|
+
|
|
2866
|
+
// src/react/components/tooltip.tsx
|
|
2867
|
+
import { Fragment as Fragment5, jsx as jsx15, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2868
|
+
var OFFSET = 12;
|
|
2869
|
+
var DefaultContent = ({
|
|
2870
|
+
label,
|
|
2871
|
+
formattedValue,
|
|
2872
|
+
drillable,
|
|
2873
|
+
noData,
|
|
2874
|
+
drillHint
|
|
2875
|
+
}) => {
|
|
2876
|
+
return /* @__PURE__ */ jsxs7(Fragment5, { children: [
|
|
2877
|
+
/* @__PURE__ */ jsx15("strong", { children: label }),
|
|
2878
|
+
/* @__PURE__ */ jsx15("div", { children: formattedValue === "" ? noData : formattedValue }),
|
|
2879
|
+
drillable && /* @__PURE__ */ jsx15("div", { className: "cm-tooltip__hint", children: drillHint })
|
|
2880
|
+
] });
|
|
2881
|
+
};
|
|
2882
|
+
var Tooltip = ({ className, children }) => {
|
|
2883
|
+
const { choropleth, store, classNames } = useChoroplethContext();
|
|
2884
|
+
const point = useMapStore(store, (s) => s.pointer);
|
|
2885
|
+
const hovered = useMapStore(store, (s) => s.hovered);
|
|
2886
|
+
const touch = useMapStore(store, (s) => s.pointerType === "touch");
|
|
2887
|
+
const context = hovered === null ? void 0 : choropleth.getItemContext(hovered);
|
|
2888
|
+
if (context === void 0 || point === null) return null;
|
|
2889
|
+
const flipX = point.x > choropleth.width / 2;
|
|
2890
|
+
const flipY = point.y > choropleth.height / 2;
|
|
2891
|
+
return /* @__PURE__ */ jsx15(
|
|
2892
|
+
"div",
|
|
2893
|
+
{
|
|
2894
|
+
role: "tooltip",
|
|
2895
|
+
className: cx(
|
|
2896
|
+
"cm-tooltip",
|
|
2897
|
+
// Only the default content has a box; if you pass `children`, the look is yours.
|
|
2898
|
+
children === void 0 && "cm-tooltip--default",
|
|
2899
|
+
classNames.tooltip,
|
|
2900
|
+
className
|
|
2901
|
+
),
|
|
2902
|
+
style: {
|
|
2903
|
+
position: "absolute",
|
|
2904
|
+
left: point.x + (flipX ? -OFFSET : OFFSET),
|
|
2905
|
+
top: point.y + (flipY ? -OFFSET : OFFSET),
|
|
2906
|
+
transform: `translate(${flipX ? "-100%" : "0"}, ${flipY ? "-100%" : "0"})`,
|
|
2907
|
+
pointerEvents: "none"
|
|
2908
|
+
},
|
|
2909
|
+
children: children === void 0 ? /* @__PURE__ */ jsx15(
|
|
2910
|
+
DefaultContent,
|
|
2911
|
+
{
|
|
2912
|
+
...context,
|
|
2913
|
+
noData: choropleth.labels.noData,
|
|
2914
|
+
drillHint: touch ? choropleth.labels.tapAgainHint : choropleth.labels.drillHint
|
|
2915
|
+
}
|
|
2916
|
+
) : children(context)
|
|
2917
|
+
}
|
|
2918
|
+
);
|
|
2919
|
+
};
|
|
2920
|
+
|
|
2921
|
+
// src/react/components/zoom-controls.tsx
|
|
2922
|
+
import { jsx as jsx16, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2923
|
+
var CORNERS2 = {
|
|
2924
|
+
"top-left": { top: 12, left: 12 },
|
|
2925
|
+
"top-right": { top: 12, right: 12 },
|
|
2926
|
+
"bottom-left": { bottom: 12, left: 12 },
|
|
2927
|
+
"bottom-right": { bottom: 12, right: 12 }
|
|
2928
|
+
};
|
|
2929
|
+
var STEP = 2;
|
|
2930
|
+
var ZoomControls = ({
|
|
2931
|
+
position = "bottom-right",
|
|
2932
|
+
reset = true,
|
|
2933
|
+
className
|
|
2934
|
+
}) => {
|
|
2935
|
+
const { choropleth, store, classNames } = useChoroplethContext();
|
|
2936
|
+
const { zoom: zoom2, labels } = choropleth;
|
|
2937
|
+
const camera = useMapStore(store, (s) => s.camera);
|
|
2938
|
+
if (zoom2 === null) return null;
|
|
2939
|
+
const atMax = camera.k >= zoom2.max - 1e-6;
|
|
2940
|
+
const atMin = camera.k <= zoom2.min + 1e-6;
|
|
2941
|
+
return /* @__PURE__ */ jsxs8(
|
|
2942
|
+
"div",
|
|
2943
|
+
{
|
|
2944
|
+
className: cx("cm-zoom-controls", classNames.controls, className),
|
|
2945
|
+
style: { position: "absolute", ...CORNERS2[position] },
|
|
2946
|
+
children: [
|
|
2947
|
+
/* @__PURE__ */ jsx16(
|
|
2948
|
+
"button",
|
|
2949
|
+
{
|
|
2950
|
+
type: "button",
|
|
2951
|
+
className: cx("cm-zoom-button", classNames.controlButton),
|
|
2952
|
+
"aria-label": labels.zoomIn,
|
|
2953
|
+
disabled: atMax,
|
|
2954
|
+
onClick: () => void store.getZoom()?.zoomBy(STEP),
|
|
2955
|
+
children: /* @__PURE__ */ jsx16("span", { "aria-hidden": "true", children: "+" })
|
|
2956
|
+
}
|
|
2957
|
+
),
|
|
2958
|
+
/* @__PURE__ */ jsx16(
|
|
2959
|
+
"button",
|
|
2960
|
+
{
|
|
2961
|
+
type: "button",
|
|
2962
|
+
className: cx("cm-zoom-button", classNames.controlButton),
|
|
2963
|
+
"aria-label": labels.zoomOut,
|
|
2964
|
+
disabled: atMin,
|
|
2965
|
+
onClick: () => void store.getZoom()?.zoomBy(1 / STEP),
|
|
2966
|
+
children: /* @__PURE__ */ jsx16("span", { "aria-hidden": "true", children: "\u2212" })
|
|
2967
|
+
}
|
|
2968
|
+
),
|
|
2969
|
+
reset && /* @__PURE__ */ jsx16(
|
|
2970
|
+
"button",
|
|
2971
|
+
{
|
|
2972
|
+
type: "button",
|
|
2973
|
+
className: cx("cm-zoom-button", classNames.controlButton),
|
|
2974
|
+
"aria-label": labels.resetView,
|
|
2975
|
+
disabled: isFitTransform(camera),
|
|
2976
|
+
onClick: () => void store.getZoom()?.resetView(),
|
|
2977
|
+
children: /* @__PURE__ */ jsx16("span", { "aria-hidden": "true", children: "\u27F2" })
|
|
2978
|
+
}
|
|
2979
|
+
)
|
|
2980
|
+
]
|
|
2981
|
+
}
|
|
2982
|
+
);
|
|
2983
|
+
};
|
|
2984
|
+
|
|
2985
|
+
// src/react/choropleth.ts
|
|
2986
|
+
var Choropleth = {
|
|
2987
|
+
Root,
|
|
2988
|
+
Svg,
|
|
2989
|
+
Regions,
|
|
2990
|
+
Borders,
|
|
2991
|
+
Defs,
|
|
2992
|
+
Patterns,
|
|
2993
|
+
Breadcrumb,
|
|
2994
|
+
Bubbles,
|
|
2995
|
+
DrillLayers,
|
|
2996
|
+
FallbackPoints,
|
|
2997
|
+
Selection,
|
|
2998
|
+
Viewport,
|
|
2999
|
+
ZoomControls,
|
|
3000
|
+
GestureHint,
|
|
3001
|
+
Legend,
|
|
3002
|
+
Tooltip
|
|
3003
|
+
};
|
|
3004
|
+
|
|
3005
|
+
// src/react/choropleth-map.tsx
|
|
3006
|
+
import {
|
|
3007
|
+
forwardRef as forwardRef2
|
|
3008
|
+
} from "react";
|
|
3009
|
+
import { jsx as jsx17, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
3010
|
+
var CORNERS3 = {
|
|
3011
|
+
"top-left": { top: 12, left: 12 },
|
|
3012
|
+
"top-right": { top: 12, right: 12 },
|
|
3013
|
+
"bottom-left": { bottom: 12, left: 12 },
|
|
3014
|
+
"bottom-right": { bottom: 12, right: 12 }
|
|
3015
|
+
};
|
|
3016
|
+
var ChoroplethMapInner = ({
|
|
3017
|
+
legend,
|
|
3018
|
+
breadcrumb,
|
|
3019
|
+
renderTooltip,
|
|
3020
|
+
className,
|
|
3021
|
+
style,
|
|
3022
|
+
unstyled,
|
|
3023
|
+
theme,
|
|
3024
|
+
emptyPattern,
|
|
3025
|
+
classNames,
|
|
3026
|
+
ariaLabel,
|
|
3027
|
+
...options
|
|
3028
|
+
}, ref) => {
|
|
3029
|
+
const legendOptions = legend === false ? null : legend ?? {};
|
|
3030
|
+
const controls = resolveControls(options.zoom);
|
|
3031
|
+
return /* @__PURE__ */ jsxs9(
|
|
3032
|
+
Root,
|
|
3033
|
+
{
|
|
3034
|
+
ref,
|
|
3035
|
+
...options,
|
|
3036
|
+
emptyLabel: options.emptyLabel ?? resolveLabels(options.labels).noData,
|
|
3037
|
+
className,
|
|
3038
|
+
style,
|
|
3039
|
+
unstyled,
|
|
3040
|
+
theme,
|
|
3041
|
+
emptyPattern,
|
|
3042
|
+
classNames,
|
|
3043
|
+
children: [
|
|
3044
|
+
/* @__PURE__ */ jsx17(Svg, { ariaLabel }),
|
|
3045
|
+
legendOptions !== null && /* @__PURE__ */ jsx17("div", { style: { position: "absolute", ...CORNERS3[legendOptions.position ?? "bottom-left"] }, children: /* @__PURE__ */ jsx17(
|
|
3046
|
+
Legend,
|
|
3047
|
+
{
|
|
3048
|
+
title: legendOptions.title,
|
|
3049
|
+
showCount: legendOptions.showCount,
|
|
3050
|
+
variant: legendOptions.variant,
|
|
3051
|
+
interactive: legendOptions.interactive
|
|
3052
|
+
}
|
|
3053
|
+
) }),
|
|
3054
|
+
options.drill !== void 0 && breadcrumb !== false && /* @__PURE__ */ jsx17(Breadcrumb, { position: breadcrumb?.position ?? "top-left" }),
|
|
3055
|
+
controls !== null && /* @__PURE__ */ jsx17(ZoomControls, { position: controls.position, reset: controls.reset }),
|
|
3056
|
+
/* @__PURE__ */ jsx17(GestureHint, {}),
|
|
3057
|
+
/* @__PURE__ */ jsx17(Tooltip, { children: renderTooltip })
|
|
3058
|
+
]
|
|
3059
|
+
}
|
|
3060
|
+
);
|
|
3061
|
+
};
|
|
3062
|
+
var ChoroplethMap = forwardRef2(ChoroplethMapInner);
|
|
3063
|
+
export {
|
|
3064
|
+
Borders,
|
|
3065
|
+
Breadcrumb,
|
|
3066
|
+
Bubbles,
|
|
3067
|
+
Choropleth,
|
|
3068
|
+
ChoroplethMap,
|
|
3069
|
+
DRILL_BRIDGE,
|
|
3070
|
+
Defs,
|
|
3071
|
+
DrillLayers,
|
|
3072
|
+
FALLBACK_POINT_RADIUS,
|
|
3073
|
+
FallbackPoints,
|
|
3074
|
+
GestureHint,
|
|
3075
|
+
Legend,
|
|
3076
|
+
Patterns,
|
|
3077
|
+
REVEAL_MS,
|
|
3078
|
+
ROOT_LEVEL,
|
|
3079
|
+
Regions,
|
|
3080
|
+
Root,
|
|
3081
|
+
Selection,
|
|
3082
|
+
Svg,
|
|
3083
|
+
THEMES,
|
|
3084
|
+
Tooltip,
|
|
3085
|
+
Viewport,
|
|
3086
|
+
ZoomControls,
|
|
3087
|
+
buildDrillEvent,
|
|
3088
|
+
clearGeographyCache,
|
|
3089
|
+
createDrillBridge,
|
|
3090
|
+
createMapStore,
|
|
3091
|
+
defineDrillLevels,
|
|
3092
|
+
defineTheme,
|
|
3093
|
+
extendTheme,
|
|
3094
|
+
isDrillController,
|
|
3095
|
+
isDrillable,
|
|
3096
|
+
sourceDepth,
|
|
3097
|
+
themeToCssVars,
|
|
3098
|
+
useCamera,
|
|
3099
|
+
useChoropleth,
|
|
3100
|
+
useChoroplethApi,
|
|
3101
|
+
useContainerSize,
|
|
3102
|
+
useDefId,
|
|
3103
|
+
useDrillChoreography,
|
|
3104
|
+
useDrillDown,
|
|
3105
|
+
useDrillEngine,
|
|
3106
|
+
useGeography,
|
|
3107
|
+
useMapStore,
|
|
3108
|
+
useZoom
|
|
3109
|
+
};
|
|
3110
|
+
//# sourceMappingURL=index.js.map
|