Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions _config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ source_dir: source
public_dir: public
tag_dir: tags
archive_dir: articles
category_dir: categories
author_dir: authors
techcast_dir: techcasts
code_dir: downloads/code
Expand All @@ -35,10 +34,6 @@ author_generator:
per_page: 25
url_map:

category_generator:
per_page: 25
order_by: -date

tag_generator:
per_page: 25
order_by: -date
Expand Down
79 changes: 79 additions & 0 deletions scripts/category_chart_helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
'use strict';

/**
* サイトの全投稿を集計し、時間軸ごとのカテゴリ別投稿データを生成する
* - 2018年以前は年ごと、2019年以降は四半期ごと
* @returns {object} { quarters: string[], series: object[], categories: string[] }
*/
function getQuarterlyCategoryData() {
const posts = this.site.posts.sort('date', 1); // 日付順にソート
if (!posts.length) {
return { quarters: [], series: [], categories: [] };
}

const dataByTimeBucket = new Map();

// 1. 全投稿をループして、時間軸ごとにカテゴリ別投稿数を集計
posts.forEach(post => {
const year = post.date.year();
let timeKey;

if (year >= 2019) {
// 2019年以降は四半期ごと
const quarter = Math.floor(post.date.month() / 3) + 1;
timeKey = `${year}-Q${quarter}`;
} else {
// 2018年以前は年ごと
timeKey = year.toString();
}

if (!dataByTimeBucket.has(timeKey)) {
dataByTimeBucket.set(timeKey, new Map());
}

const bucketData = dataByTimeBucket.get(timeKey);
const postCategories = post.categories.map(cat => cat.name);
if (!postCategories.length) return;

postCategories.forEach(catName => {
const currentCount = bucketData.get(catName) || 0;
bucketData.set(catName, currentCount + 1);
});
});

// 2. サイトの全カテゴリを取得し、「合計記事数」で降順にソートする
const sortedCategoryObjects = this.site.categories.toArray().sort((a, b) => b.length - a.length);
const sortedCategoryNames = sortedCategoryObjects.map(cat => cat.name);

// 3. X軸のラベル(時間軸)を生成し、ソートする
const sortedTimeKeys = Array.from(dataByTimeBucket.keys()).sort();

// 4. EChartsのseries形式にデータを整形
const series = sortedCategoryObjects.map(category => {
const catName = category.name;
const data = sortedTimeKeys.map(timeKey => {
const bucketData = dataByTimeBucket.get(timeKey);
return bucketData.get(catName) || 0; // その期間に投稿がなければ0
});

return {
name: catName,
type: 'line',
stack: 'Total',
areaStyle: {},
emphasis: {
focus: 'series'
},
data: data
};
});

return {
quarters: sortedTimeKeys, // キー名はEJS側と合わせるため'quarters'のまま
series: series,
categories: sortedCategoryNames
};
}

// ヘルパーとして登録
hexo.extend.helper.register('get_quarterly_category_data', getQuarterlyCategoryData);
4 changes: 4 additions & 0 deletions source/categories/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
title: カテゴリ一覧
layout: categories
---
50 changes: 44 additions & 6 deletions themes/future/layout/categories.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,50 @@
<li><a href="/">Home</a></li>
<li class="active">Categories</li>
</ul>

<section id="main" class="margin-top-30">
<h2 class="list-page">カテゴリから記事を探す</h2>
<ul class="summary">
<li><span class="summary-count"><%= count_categories() %></span><br><span class="summary-label">カテゴリ</span></li>
</ul>
<%- list_categories() %>

<div class="widget">
<h2 class="list-page">カテゴリ別 投稿数の推移(四半期ごと)</h2>
<div id="category-chart" style="width: 100%; height: 400px;"></div>
</div>

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/echarts.min.js"></script>

<script type="text/javascript">
const chartData = <%- JSON.stringify(get_quarterly_category_data()) %>;
const myChart = echarts.init(document.getElementById('category-chart'));
const option = {
tooltip: { trigger: 'axis', axisPointer: { type: 'cross', label: { backgroundColor: '#6a7985' } } },
legend: { data: chartData.categories, type: 'scroll', orient: 'horizontal', left: 'center', top: 'bottom' },
grid: { left: '3%', right: '4%', bottom: '15%', containLabel: true },
xAxis: [ { type: 'category', boundaryGap: false, data: chartData.quarters } ],
yAxis: [ { name: '投稿数', type: 'value' } ],
series: chartData.series
};
myChart.setOption(option);
</script>

<br>
<div class="widget">
<h2 class="list-page">カテゴリから記事を探す</h2>
<div class="widget">
<ul class="nav margin-bottom-40">
<%
const compareFunc = (a, b) => b.posts.length - a.posts.length;
const categoryRanks = site.categories.data.sort(compareFunc)

let categoryListHTML = "";
categoryRanks.forEach(function(c) {
let className = "";
if (is_category(c.name)) {
className = "category-list-current"
}
categoryListHTML += `<li class="${className}"><a href="${url_for(c.path)}">${c.name} (${c.length})</a></li>\n`
});
%>
<%- categoryListHTML %>
</ul>
</div>
</section>
</div>

4 changes: 2 additions & 2 deletions themes/future/layout/category.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
<ul class="breadcrumb">
<li><a href="/">Home</a></li>
<% if (page.current === 1) { %>
<li><a href="/articles">Categories</a></li>
<li><a href="/categories">Categories</a></li>
<li class="active"><%- page.category %></li>
<% } else { %>
<li><a href="/articles">Category</a></li>
<li><a href="/categories">Category</a></li>
<li><a href="/categories/<%- page.category %>"><%- page.category %></a></li>
<li class="active"><%= page.current %>ページ目</li>
<% } %>
Expand Down
Loading