r111/app.js · 约 4025 字符
1/* r111 图表工具大全 · 公共运行时(纯原生 JS,零依赖) */2var $ = function (s, r) { return (r || document).querySelector(s); };3var $$ = function (s, r) { return Array.prototype.slice.call((r || document).querySelectorAll(s)); };45/* ---------------- 数据集加载器:优先 fetch 读取 data/*.json ---------------- */6var DS = (function () {7 var cache = {};8 var INDEX = null;910 function fromFallback(name) {11 var f = window.__DATA__ || {};12 if (f[name]) return f[name];13 throw new Error('数据集不存在:' + name);14 }15 function get(name) {16 if (cache[name]) return Promise.resolve(cache[name]);17 var url = 'data/' + name + '.json';18 var p;19 try {20 p = fetch(url).then(function (r) {21 if (!r.ok) throw new Error('HTTP ' + r.status);22 return r.json();23 });24 } catch (e) {25 p = Promise.reject(e);26 }27 return p.then(function (j) {28 j.__from = 'json';29 cache[name] = j;30 return j;31 })['catch'](function () {32 /* file:// 协议或离线打开时 fetch 会失败,回落到内置副本,保证 zip 解压后双击即可运行 */33 var j = fromFallback(name);34 j.__from = 'inline';35 cache[name] = j;36 return j;37 });38 }39 function index() {40 if (INDEX) return Promise.resolve(INDEX);41 return get('index').then(function (j) { INDEX = j.datasets || []; return INDEX; });42 }43 function all(names) { return Promise.all(names.map(get)); }44 return { get: get, all: all, index: index, cache: cache };45})();4647/* ---------------- 通用工具 ---------------- */48function toast(msg, ms) {49 var t = $('#toast');50 if (!t) return;51 t.textContent = msg;52 t.classList.add('on');53 clearTimeout(t._t);54 t._t = setTimeout(function () { t.classList.remove('on'); }, ms || 1600);55}56function openSheet(id) {57 var s = $(id), m = $('#mask');58 if (s) s.classList.add('on');59 if (m) m.classList.add('on');60}61function closeSheet() {62 $$('.sheet').forEach(function (s) { s.classList.remove('on'); });63 var m = $('#mask');64 if (m) m.classList.remove('on');65}66function segGroup(root, cb) {67 var box = typeof root === 'string' ? $(root) : root;68 if (!box) return;69 box.addEventListener('click', function (e) {70 var b = e.target.closest('button');71 if (!b || b.classList.contains('on')) return;72 Array.prototype.forEach.call(box.children, function (c) { c.classList.remove('on'); });73 b.classList.add('on');74 cb(b.dataset.v, b);75 });76}77function segValue(root) {78 var box = typeof root === 'string' ? $(root) : root;79 var on = box && box.querySelector('.on');80 return on ? on.dataset.v : null;81}82function loading(host, text) {83 if (!host) return;84 host.innerHTML = '<div class="loading"><div class="spin"></div>' + (text || '数据加载中') + '</div>';85}86function failBox(host, err) {87 if (!host) return;88 host.innerHTML = '<div class="loading">数据加载失败:' + (err && err.message ? err.message : err) + '</div>';89}90function esc(s) {91 return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');92}93/* 简易 JSON 语法高亮 */94function hlJSON(obj) {95 var txt = esc(JSON.stringify(obj, null, 2));96 txt = txt.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"\s*:)/g, '<span class="k">$1</span>');97 txt = txt.replace(/:\s*("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*")/g, ': <span class="s">$1</span>');98 txt = txt.replace(/:\s*(-?\d+\.?\d*([eE][+-]?\d+)?)/g, ': <span class="n">$1</span>');99 txt = txt.replace(/:\s*(true|false|null)/g, ': <span class="n">$1</span>');100 return txt;101}102/* 数据来源徽标:告诉用户当前数据是从 JSON 文件读取还是内置回落 */103function srcBadge(ds) {104 return ds && ds.__from === 'json'105 ? '<span class="tag g">data/' + ds.id + '.json</span>'106 : '<span class="tag o">内置副本(file:// 离线)</span>';107}108109document.addEventListener('click', function (e) {110 if (e.target.id === 'mask') closeSheet();111 if (e.target.classList && e.target.classList.contains('x')) closeSheet();112});113