(async () => {
const avBlockId = '20240727220211-p1awn4b';
const chartBlockId = '20240805130000-vhz1aei';
const autoFreshDelay = 1000;
let option = {
"title": {
"text": "网站访问统计",
"left": "center",
"top": "top"
},
"tooltip": {
"trigger": "axis"
},
"xAxis": {
"type": "category",
"data": [
"第一季度",
"第二季度",
"第三季度",
"第四季度"
],
"axisLabel": {
"rotate": 0
}
},
"yAxis": {
"type": "value"
},
"series": [
{
"data": [
1495,
1678,
1750,
1096
],
"type": "bar",
"barWidth": 30,
"label": {
"show": true,
"position": "top"
},
"name": "2020"
},
{
"data": [
1208,
1225,
1098,
1326
],
"type": "bar",
"barWidth": 30,
"label": {
"show": true,
"position": "top"
},
"name": "2021"
},
{
"data": [
2548,
1574,
2534,
1038
],
"type": "bar",
"barWidth": 30,
"label": {
"show": true,
"position": "top"
},
"name": "2022"
}
],
"legend": {
"data": [
"2020",
"2021",
"2022"
],
"left": "center",
"top": "bottom"
}
}
await getAVDataByBlockId(avBlockId, (av) => {
option.title.text = av.name;
option.xAxis.data = av.keyValues[0].values.map(item => item.block.content);
option.series = av.keyValues.slice(1).map(item => {
const data = item.values.map(item => item[item.type].content);
return {
"data": data,
"type": "bar",
"barWidth": 30,
"label": {
"show": true,
"position": "top"
},
"name": item.key.name
}
});
});
if(autoFreshDelay > 0 && !window['__chat_observe__' + avBlockId]) {
window['__chat_observe__' + avBlockId] = observeDOMChanges(document.querySelector('.layout__center div[data-node-id="'+avBlockId+'"]'), ()=>{
freshChart(chartBlockId);
}, autoFreshDelay, {attributes: false});
}
console.log('render chart start');
async function getAVDataByBlockId(blockId, callback) {
const block = await fetchSyncPost('/api/query/sql', {"stmt": `SELECT * FROM blocks WHERE id = '${blockId}'`})
const markdown = block.data[0]?.markdown;
if(markdown){
const avId = getDataAvIdFromHtml(markdown);
const av = await fetchSyncPost('/api/file/getFile', {"path":`/data/storage/av/${avId}.json`});
if(av){
if(typeof callback === 'function') callback(av);
} else {
option = "未找到av-id=" + avId + "的数据库文件";
}
} else {
option = "未找到id=" + avBlockId + "的数据库块";
}
}
async function fetchSyncPost (url, data) {
const init = {
method: "POST",
};
if (data) {
if (data instanceof FormData) {
init.body = data;
} else {
init.body = JSON.stringify(data);
}
}
const res = await fetch(url, init);
const res2 = await res.json();
return res2;
}
function getDataAvIdFromHtml(htmlString) {
const match = htmlString.match(/data-av-id="([^"]+)"/);
if (match && match[1]) {
return match[1];
}
return "";
}
async function freshChart(chartBlockId) {
const ZWSP = "\u200b";
const looseJsonParse = (text) => {
return Function(`"use strict";return (${text})`)();
};
const e = document.querySelector('.layout__center div[data-subtype="echarts"][data-node-id="'+chartBlockId+'"]')
let width = undefined;
if (e.firstElementChild.clientWidth === 0) {
const tabElement = hasClosestByClassName(e, "layout-tab-container", true);
if (tabElement) {
Array.from(tabElement.children).find(item => {
if (item.classList.contains("protyle") && !item.classList.contains("fn__none")) {
width = item.querySelector(".protyle-wysiwyg").firstElementChild.clientWidth;
return true;
}
});
}
}
const wysiswgElement = hasClosestByClassName(e, "protyle-wysiwyg", true);
if (!e.firstElementChild.classList.contains("protyle-icons")) {
e.insertAdjacentHTML("afterbegin", genIconHTML(wysiswgElement));
}
const renderElement = e.firstElementChild.nextElementSibling;
try {
renderElement.style.height = e.style.height;
const option = await looseJsonParse(Lute.UnEscapeHTMLStr(e.getAttribute("data-content")));
window.echarts.init(renderElement, window.siyuan.config.appearance.mode === 1 ? "dark" : undefined, {width}).setOption(option);
e.setAttribute("data-render", "true");
renderElement.classList.remove("ft__error");
if (!renderElement.textContent.endsWith(ZWSP)) {
renderElement.firstElementChild.insertAdjacentText("beforeend", ZWSP);
}
} catch (error) {
window.echarts.dispose(renderElement);
renderElement.classList.add("ft__error");
renderElement.innerHTML = `echarts render error: <br>${error}`;
}
}
function hasClosestByClassName(element, className, top = false) {
if (!element) {
return false;
}
if (element.nodeType === 3) {
element = element.parentElement;
}
let e = element;
let isClosest = false;
while (e && !isClosest && (top ? e.tagName !== "BODY" : !e.classList.contains("protyle-wysiwyg"))) {
if (e.classList?.contains(className)) {
isClosest = true;
} else {
e = e.parentElement;
}
}
return isClosest && e;
}
function genIconHTML(element) {
let enable = true;
if (element) {
const readonly = element.getAttribute("contenteditable");
if (typeof readonly === "string") {
enable = element.getAttribute("contenteditable") === "true";
} else {
return '<div class="protyle-icons"></div>';
}
}
return `<div class="protyle-icons">
<span aria-label="${window.siyuan.languages.edit}" class="b3-tooltips__nw b3-tooltips protyle-icon protyle-icon--first protyle-action__edit${enable ? "" : " fn__none"}"><svg><use xlink:href="#iconEdit"></use></svg></span>
<span aria-label="${window.siyuan.languages.more}" class="b3-tooltips__nw b3-tooltips protyle-icon protyle-action__menu protyle-icon--last${enable ? "" : " protyle-icon--first"}"><svg><use xlink:href="#iconMore"></use></svg></span>
</div>`;
}
let observeTimer = null;
function observeDOMChanges(targetNode, callback, debounceTime = 1000, options = {}) {
const defaultOptions = {
attributes: true,
childList: true,
subtree: true,
};
const config = Object.assign({}, defaultOptions, options);
const observer = new MutationObserver((mutationsList) => {
if(observeTimer) {
clearTimeout(observeTimer);
}
observeTimer = setTimeout(() => {
callback(mutationsList);
}, debounceTime);
});
observer.observe(targetNode, config);
return () => {
observer.disconnect();
};
}
return option;
})()