Created
January 22, 2021 07:19
-
-
Save Kcars/526996bbc4b8cbda3958bea491bcd650 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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