Skip to content

Instantly share code, notes, and snippets.

@Kcars
Created January 22, 2021 07:19
Show Gist options
  • Save Kcars/526996bbc4b8cbda3958bea491bcd650 to your computer and use it in GitHub Desktop.
Save Kcars/526996bbc4b8cbda3958bea491bcd650 to your computer and use it in GitHub Desktop.
const rq = require("request");
const fs = require("fs");
////
const d3 = require("d3");
////
const jsdom = require("jsdom");
////
const canvas = require("canvas");
const Canvas = canvas.createCanvas;
canvas.registerFont("./fonts/KosugiMaru-Regular.ttf", { family: "MotoyaLMaru" });
const tc = Canvas(1280, 720);
const context = tc.getContext("2d");
////
const { fetch } = require('node-fetch');
const { DOMParser } = require("xmldom");
const { Canvg, presets } = require("canvg");
const { JSDOM } = jsdom;
const window = (new JSDOM(`<body><div></div></body>`, { pretendToBeVisual: true })).window;
const main = window.document.querySelector("div");
////
const KEYWORDS = require("./keywords.json");
////
const preset = presets.node({
DOMParser,
canvas,
fetch
});
////
const LABELS = {
"pity": "不憫",
"fire": "炎上",
"poop": "うんち",
"discord": "不仲",
"likes": "好き",
"great": "えらい",
"comeback": "おかえり",
"flag": "フラグ",
"nice": "ナイス",
"tete": "てぇてぇ",
"genius": "天才",
"helpful": "助かる",
"cute": "カワイイ",
"sexy": "センシティブ",
"know": "わかる"
}
////
let search_date = null;
let dd = new Date();
dd.setDate(dd.getDate() - 1);
search_date = dd.toISOString().slice(0, 10);
function getChannelNameFromID(channel_id) {
if (channel_id == 'UCX7YkU9nEeaoZbkVLVajcMg') {
return "にじさんじ"
} else if (channel_id == 'UCD-miitqNY3nyukJ4Fnf4_A') {
return "月ノ美兎"
} else if (channel_id == 'UC0g1AE0DOjBYnLhkgoRWN1w') {
return "本間ひまわり"
} else if (channel_id == 'UCSFCh5NL4qXrAy9u-u2lX3g') {
return "葛葉"
} else if (channel_id == 'UCoztvTULBYd3WmStqYeoHcA') {
return "笹木咲"
} else if (channel_id == 'UCspv01oxUFf_MTSipURRhkA') {
return "叶"
} else if (channel_id == 'UCwQ9Uv-m8xkE5PzRc7Bqx3Q') {
return "御伽原江良"
} else if (channel_id == 'UC_a1ZYZ8ZTXpjg9xUY9sj8w') {
return "鈴原るる"
} else if (channel_id == 'UCZ1xuCK1kNmn5RzPYIZop3w') {
return "リゼ"
} else if (channel_id == 'UCHVXbQzkl3rDfsXWo8xi2qw') {
return "アンジュ"
} else if (channel_id == 'UC_4tXjqecqox5Uc05ncxpxg') {
return "椎名唯華"
} else if (channel_id == 'UCwokZsOK_uEre70XayaFnzA') {
return "鈴鹿詩子"
} else if (channel_id == 'UCt5-0i4AVHXaWJrL8Wql3mw') {
return "緑仙"
} else if (channel_id == 'UCdpUojq0KWZCN9bxXnZwz5w') {
return "アルス・アルマル"
} else if (channel_id == 'UCKMYISTJAQ8xTplUPHiABlA') {
return "社築"
} else if (channel_id == 'UCsg-YqdqQ-KFF0LNk23BY4A') {
return "樋口楓"
} else if (channel_id == 'UCXRlIK3Cw_TJIQC5kSJJQMg') {
return "戌亥とこ"
} else if (channel_id == 'UC9V3Y3_uzU5e-usObb6IE1w') {
return "星川サラ"
} else if (channel_id == 'UCLO9QDxVL4bnvRRsz6K4bsQ') {
return "勇気ちひろ"
} else if (channel_id == 'UCPvGypSgfDkVe7JG2KygK7A') {
return "竜胆尊"
} else if (channel_id == 'UCCVwhI5trmaSxfcze_Ovzfw') {
return "夢月ロア"
} else if (channel_id == 'UCjlmCrq4TP1I4xguOtJ-31w') {
return "でびでび・でびる"
} else if (channel_id == 'UC1zFJrfEKvCixhsjNSb1toQ') {
return "シスター・クレア"
} else if (channel_id == 'UC6oDys1BGgBsIC3WhG1BovQ') {
return "静凛"
} else if (channel_id == 'UCV5ZZlLjk5MKGg3L0n0vbzw') {
return "鷹宮リオン"
} else if (channel_id == 'UCmovZ2th3Sqpd00F5RdeigQ') {
return "加賀美ハヤト"
} else if (channel_id == 'UCb5JxV6vKlYVknoJB8TnyYg') {
return "黛灰"
} else if (channel_id == 'UCbc8fwhdUNlqi-J99ISYu4A') {
return "ベルモンド"
} else if (channel_id == 'UC8C1LLhBhf_E2IBPLSDJXlQ') {
return "健屋花那"
} else if (channel_id == 'UCeShTCVgZyq2lsBW9QwIJcw') {
return "郡道美玲"
} else if (channel_id == 'UCL34fAoFim9oHLbVzMKFavQ') {
return "夜見れな"
} else if (channel_id == 'UCJubINhCcFXlsBwnHp0wl_g') {
return "舞元啓介"
} else if (channel_id == 'UCv1fFr156jc65EMiLbaLImw') {
return "剣持刀也"
} else if (channel_id == 'UCsFn_ueskBkMCEyzCEqAOvg') {
return "花畑チャイカ"
} else if (channel_id == 'UCo7TRj3cS-f_1D9ZDmuTsjw') {
return "町田ちま"
} else if (channel_id == 'UCNW1Ex0r6HsWRD4LCtPwvoQ') {
return "三枝明那"
} else if (channel_id == 'UCUc8GZfFxtmk7ZwSO7ccQ0g') {
return "ニュイ"
} else if (channel_id == 'UChUJbHiTVeGrSkTdBzVfNCQ') {
return "ジョー・力一"
} else if (channel_id == 'UC53UDnhAAYwvNO7j_2Ju1cQ') {
return "ドーラ"
} else if (channel_id == 'UCIytNcoz4pWzXfLda0DoULQ') {
return "エクス"
} else if (channel_id == 'UCwcyyxn6h9ex4sMXGtpQE_g') {
return "メリッサ"
} else if (channel_id == 'UCBiqkFJljoxAj10SoP2w2Cg') {
return "文野環"
} else if (channel_id == 'UCkIimWZ9gBJRamKF0rmPU8w') {
return "天宮こころ"
} else if (channel_id == 'UCuep1JCrMvSxOGgGhBfJuYw') {
return "フレン"
} else if (channel_id == 'UCt0clH12Xk1-Ej5PXKGfdPA') {
return "物述有栖"
} else if (channel_id == 'UC6wvdADTJ88OfIbJYIpAaDA') {
return "不破湊"
} else if (channel_id == 'UCmZ1Rbthn-6Jm_qOGjYsh5A') {
return "イブラヒム"
} else if (channel_id == 'UCGYAYLDE7TZiiC8U6teciDQ') {
return "葉加瀬冬雪"
} else if (channel_id == 'UCTIE7LM5X15NVugV7Krp9Hw') {
return "夢追翔"
} else if (channel_id == 'UCveZ9Ic1VtcXbsyaBgxPMvg') {
return "童田明治"
} else if (channel_id == 'UCIG9rDtgR45VCZmYnd-4DUw') {
return "ラトナ・プティ"
} else if (channel_id == 'UCtpB6Bvhs1Um93ziEDACQ8g') {
return "森中花咲"
} else if (channel_id == 'UC9EjSJ8pvxtvPdxLOElv73w') {
return "魔界ノりりむ"
} else if (channel_id == 'UCYKP16oMX9KKPbrNgo_Kgag') {
return "える"
} else if (channel_id == 'UCfki3lMEF6SGBFiFfo9kvUA') {
return "ましろ"
} else if (channel_id == 'UCuvk5PilcvDECU7dDZhQiEw') {
return "白雪巴"
} else if (channel_id == 'UCfQVs_KuXeNAlGa3fb8rlnQ') {
return "桜凛月"
} else if (channel_id == 'UCHBhnG2G-qN0JrrWmMO2FTA') {
return "シェリン"
} else if (channel_id == 'UCnRQYHTnRLSF0cLJwMnedCg') {
return "相羽ういは"
} else if (channel_id == 'UCtnO2N4kPTXmyvedjGWdx3Q') {
return "レヴィ"
} else if (channel_id == 'UC48jH1ul-6HOrcSSfoR02fQ') {
return "夕陽リリ"
} else if (channel_id == 'UCWz0CSYCxf4MhRKPDm220AQ') {
return "神田笑一"
} else if (channel_id == 'UC3lNFeJiTq6L3UWoz4g1e-A') {
return "卯月コウ"
} else if (channel_id == 'UCl1oLKcAq93p-pwKfDGhiYQ') {
return "えま"
} else if (channel_id == 'UCmUjjW5zF1MMOhYUwwwQv9Q') {
return "宇志海いちご"
} else if (channel_id == 'UC1QgXt46-GEvtNjEC1paHnw') {
return "グウェル"
} else if (channel_id == 'UC_GCs6GARLxEHxy1w40d6VQ') {
return "家長むぎ"
} else if (channel_id == 'UCmeyo5pRj_6PXG-CsGUuWWg') {
return "黒井しば"
} else if (channel_id == 'UCkngxfPbmGyGl_RIq4FA3MQ') {
return "西園チグサ"
} else if (channel_id == 'UCXW4MqCQn-jCaxlX-nn-BYg') {
return "長尾景"
} else if (channel_id == 'UC2OacIzd2UxGHRGhdHl1Rhw') {
return "早瀬走"
} else if (channel_id == 'UCfipDDn7wY-C-SoUChgxCQQ') {
return "葉山舞鈴"
} else if (channel_id == 'UCwrjITPwG4q71HzihV2C7Nw') {
return "フミ"
} else if (channel_id == 'UCo2N7C-Z91waaR6lF3LL_jw') {
return "甲斐田晴"
} else if (channel_id == 'UC0WwEfE-jOM2rzjpdfhTzZA') {
return "愛園愛美"
} else if (channel_id == 'UCb6ObE-XGCctO3WrjRZC-cw') {
return "ルイス"
} else if (channel_id == 'UCBi8YaVyZpiKWN3_Z0dCTfQ') {
return "赤羽葉子"
} else if (channel_id == 'UCpNH2Zk2gw3JBjWAKSyZcQQ') {
return "エリー"
} else if (channel_id == 'UCRWOdwLRsenx2jLaiCAIU4A') {
return "雨森小夜"
} else if (channel_id == 'UCerkculBD7YLc_vOGrF7tKg') {
return "魔使マオ"
} else if (channel_id == 'UCryOPk2GZ1meIDt53tL30Tw') {
return "鈴木勝"
} else if (channel_id == 'UCvmppcdYf4HOv-tFQhHHJMA') {
return "モイラ"
} else if (channel_id == 'UCeK9HFcRZoTrvqcUCtccMoQ') {
return "渋谷ハジメ"
} else if (channel_id == 'UC-o-E6I3IC2q8sAoAuM6Umg') {
return "奈羅花"
} else if (channel_id == 'UCXU7YYxy_iQd3ulXyO-zC2w') {
return "伏見ガク"
} else if (channel_id == 'UCg63a3lk6PNeWhVvMRM_mrQ') {
return "小野町春香"
} else if (channel_id == 'UCHX7YpFG8rVwhsHCx34xt7w') {
return "雪城眞尋"
} else if (channel_id == 'UCRqBKoKuX30ruKAq05pCeRQ') {
return "北小路ヒスイ"
} else if (channel_id == 'UCGw7lrT-rVZCWHfdG9Frcgg') {
return "弦月藤士郎"
} else if (channel_id == 'UCpnvhOIJ6BN-vPkYU9ls-Eg') {
return "鈴谷アキ"
} else if (channel_id == 'UC_82HBGtvwN1hcGeOGHzUBQ') {
return "空星きらめ"
} else if (channel_id == 'UCllKI7VjyANuS1RXatizfLQ') {
return "山神カルタ"
} else if (channel_id == 'UCHK5wkevfaGrPr7j3g56Jmw') {
return "瀬戸美夜子"
} else if (channel_id == 'UCiSRx1a2k-0tOg-fs6gAolQ') {
return "飛鳥ひな"
} else if (channel_id == 'UC6TfqY40Xt1Y0J-N18c85qQ') {
return "安土桃"
} else if (channel_id == 'UCRV9d6YCYIMUszK-83TwxVA') {
return "轟京子"
} else if (channel_id == 'UCRcLAVTbmx2-iNcXSsupdNA') {
return "来栖夏芽"
} else if (channel_id == 'UCL_O_HXgLJx3Auteer0n0pA') {
return "周央サンゴ"
} else if (channel_id == 'UCUzJ90o1EjqUbk2pBAy0_aw') {
return "ギルザレンⅢ世"
} else if (channel_id == 'UCebT4Aq-3XWb5je1S1FvR_A') {
return "東堂コハク"
} else if (channel_id == 'UCvzVB-EYuHFXHZrObB8a_Og') {
return "矢車りね"
} else if (channel_id == 'UCe_p3YEuYJb8Np0Ip9dk-FQ') {
return "朝日南アカネ"
} else if (channel_id == 'UCufQu4q65z63IgE4cfKs1BQ') {
return "語部紡"
} else if (channel_id == 'UCoM_XmK45j504hfUWvN06Qg') {
return "成瀬鳴"
} else if (channel_id == 'UCtAvQ5U0aXyKwm2i4GqFgJg') {
return "春崎エアル"
}
}
function getQuery(dd) {
let output = {};
let must = [{ "range": { "post_time": { "time_zone": "+08:00", "gte": `${dd}T00:00:00`, "lte": `${dd}T23:59:59` } } }];
let should = [];
let aggs = { "count": { "filters": { "filters": {} }, "aggs": { "channel": { "terms": { "field": "channel_id.keyword", "size": 200 } } } } };
let total_keywords = [];
Object.keys(KEYWORDS.groups).forEach((key) => {
let item = {};
let list = KEYWORDS.groups[key];
let count_keyword = list.length;
total_keywords = total_keywords.concat(list);
if (count_keyword > 1) {
let group_should = [];
list.forEach((keyword) => {
let obj = { "match_phrase": { "content": keyword } }
group_should.push(obj);
})
item = { "bool": { "should": group_should } };
} else {
item = { "match_phrase": { "content": list[0] } }
}
aggs.count.filters.filters[key] = item;
})
total_keywords.forEach((keyword) => {
let obj = { "match_phrase": { "content": keyword } };
should.push(obj);
})
output = { "size": 0, "query": { "bool": { must, should, "minimum_should_match": 1 } }, aggs }
return output;
}
function doSearch(dd) {
return new Promise((resolve, reject) => {
let index_dd = dd.slice(0, 7).replace(/-/g, "");
let url = `http://10.0.0.19:9200/n7i-chats-${index_dd}/_search`;
let json = getQuery(dd);
console.log(`dd: ${dd} , index_dd: ${index_dd}`);
rq.post(url, { json }, (err, res) => {
let data = res.body;
let list = data.aggregations.count.buckets;
let output = {
"name": "livechat",
"children": []
}
Object.keys(list).forEach((key) => {
let name = LABELS[key];
let children = [];
list[key].channel.buckets.forEach((channel) => {
let name = getChannelNameFromID(channel.key);
let value = channel.doc_count;
let child = {
name,
value,
category: key
}
children.push(child);
})
let keyword = {
name,
children
}
if (["discord"].indexOf(key) == -1) {
output.children.push(keyword);
}
})
resolve(output);
})
})
}
function getTextSetting(context, text, font, font_size, parent_x, parent_y, parent_width, parent_height) {
context.font = `${font_size}pt '${font}'`;
let text_width = context.measureText(text).width;
let x = parent_x + ((parent_width) / 2);
let y = parent_y + ((parent_height + 10) / 2);
let size = font_size;
let opacity = text_width > parent_width ? 0 : 1;
while (opacity == 0) {
size -= 1;
context.font = `${size}pt '${font}'`;
text_width = context.measureText(text).width;
opacity = text_width > parent_width ? 0 : 1;
if (size <= 1) {
break;
}
}
return { x, y, size, opacity };
}
function drawTreeMap(dataset) {
return new Promise((resolve, reject) => {
const width = 1280 * 2;
const height = 720 * 2;
const hierarchy = d3.hierarchy(dataset)
.sum(d => d.value)
.sort((a, b) => b.value - a.value);
treemap = d3.treemap()
.size([width - 10, height])
.paddingTop(35)
.paddingLeft(10)
.paddingRight(10)
.paddingBottom(10)
.paddingInner(5)
root = treemap(hierarchy);
const categories = dataset.children.map(d => d.name);
let top = d3.select(main);
let svg = top.append("svg")
.attr("width", width)
.attr("height", height);
svg.selectAll("rect")
.data(root.leaves())
.enter()
.append("rect")
.attr("x", d => d.x0)
.attr("y", d => d.y0)
.attr("width", d => d.x1 - d.x0)
.attr("height", d => d.y1 - d.y0)
.attr("fill", (d) => {
let color = "white";
color = d.data.category == "nice" ? "#ffcc5c" : color;
color = d.data.category == "likes" ? "#fe9c8f" : color;
color = d.data.category == "know" ? "#03396c" : color;
color = d.data.category == "cute" ? "#f37736" : color;
color = d.data.category == "helpful" ? "#35a79c" : color;
color = d.data.category == "genius" ? "#4d648d" : color;
color = d.data.category == "great" ? "#8b9dc3" : color;
color = d.data.category == "comeback" ? "#76b4bd" : color;
color = d.data.category == "flag" ? "#d11141" : color;
color = d.data.category == "tete" ? "#ff8b94" : color;
color = d.data.category == "fire" ? "#d62d20" : color;
color = d.data.category == "poop" ? "#3c2f2f" : color;
color = d.data.category == "sexy" ? "#ff6f69" : color;
color = d.data.category == "pity" ? "#7f8e9e" : color;
return color;
})
svg.selectAll("text")
.data(root.leaves())
.enter()
.append("text")
.attr("x", function (d) {
let setting = getTextSetting(context, d.data.name, "MotoyaLMaru", 20, d.x0, d.y0, d.x1 - d.x0, d.y1 - d.y0);
return setting.x;
})
.attr("y", function (d) {
let setting = getTextSetting(context, d.data.name, "MotoyaLMaru", 20, d.x0, d.y0, d.x1 - d.x0, d.y1 - d.y0);
return setting.y;
})
.attr("font-size", d => {
let width = d.x1 - d.x0
let text_width = d.data.name.length * 20;
let val = text_width < width ? '20px' : '0px';
let setting = getTextSetting(context, d.data.name, "MotoyaLMaru", 20, d.x0, d.y0, d.x1 - d.x0, d.y1 - d.y0);
return `${setting.size}px`;
})
.attr("text-anchor", "middle")
.style('opacity', function (d) {
let setting = getTextSetting(context, d.data.name, "MotoyaLMaru", 20, d.x0, d.y0, d.x1 - d.x0, d.y1 - d.y0);
return setting.opacity;
})
.attr("fill", "white")
.style("font-family", "MotoyaLMaru")
.text(function (d) { return d.data.name; })
;
svg
.selectAll("titles")
.data(root.descendants().filter(function (d) { return d.depth == 1 }))
.enter()
.append("text")
.attr("x", function (d) { return d.x0 })
.attr("y", function (d) { return d.y0 + 21 })
.text(function (d) { return d.data.name })
.attr("font-size", "20px")
.style("font-family", "MotoyaLMaru")
.attr("fill", "black")
svg
.append("text")
.attr("x", 0)
.attr("y", 25)
.text(`${search_date} livechat keyword treemap`)
.attr("font-size", "30px")
.attr("fill", "black")
resolve(main.innerHTML);
})
}
function doGeneratePNGFromSVG(svg_source) {
return new Promise((resolve, reject) => {
const cc = Canvas(1280, 720);
ctx = cc.getContext('2d');
ctx.fillStyle = "#000000";
ctx.fillRect(0, 0, 1280, 720);
const v = Canvg.fromString(ctx, svg_source, preset);
v.render().then((res) => {
resolve(cc.toBuffer())
})
})
}
doSearch(search_date).then((data) => {
drawTreeMap(data).then((source) => {
doGeneratePNGFromSVG(source).then((buffer) => {
fs.writeFile(`${search_date}-treemap.png`, buffer, (res) => { });
});
});
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment