|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<script src="https://d3js.org/d3.v4.min.js"></script> |
|
<style type="text/css"> |
|
html, body, |
|
.wrapper { |
|
margin: 10px; |
|
} |
|
.selective-table__job, .selective-table__date, .selective-table__result { |
|
display: table-cell; |
|
} |
|
table { |
|
border: 1px #E3E3E3 solid; |
|
border-collapse: collapse; |
|
border-spacing: 0; |
|
} |
|
table th { |
|
padding: 5px; |
|
border: #E3E3E3 solid; |
|
border-width: 0 0 1px 1px; |
|
background: #F5F5F5; |
|
font-weight: bold; |
|
line-height: 120%; |
|
text-align: center; |
|
} |
|
table td { |
|
padding: 5px; |
|
border: 1px #E3E3E3 solid; |
|
border-width: 0 0 1px 1px; |
|
text-align: center; |
|
} |
|
ul, ol { |
|
border-top: solid gray 1px; |
|
border-bottom: solid gray 1px; |
|
padding: 0.5em 0 0.5em 1.5em; |
|
margin: 0 20px 0 0; |
|
} |
|
ul li, ol li { |
|
padding: 0.1em 0; |
|
} |
|
.__selected { |
|
color: red; |
|
} |
|
.__checked { |
|
color: red; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div class="wrapper"> |
|
<div class="selective-table"> |
|
<div class="selective-table__job"></div> |
|
<div class="selective-table__date"></div> |
|
<div class="selective-table__result"></div> |
|
</div> |
|
<script type="text/javascript"> |
|
|
|
d3.queue() |
|
.defer(d3.tsv, 'job_category.tsv') |
|
.await(function(error, job) { |
|
|
|
drawMultiTable(job); |
|
|
|
function drawMultiTable(inputData) { |
|
// クラス一覧 |
|
const tableClassName = 'selective-table'; |
|
const tableClass = '.'+tableClassName; |
|
|
|
const jobClassName = tableClassName+'__job'; |
|
const jobClass = tableClass+'__job'; |
|
const jobItemClassName = jobClassName+'__item'; |
|
const jobItemClass = '.'+jobItemClassName; |
|
|
|
const dateClassName = tableClassName+'__date'; |
|
const dateClass = tableClass+'__date'; |
|
const dateItemClassName = dateClassName+'__item'; |
|
const dateItemClass = '.'+dateItemClassName; |
|
|
|
const resultClassName = tableClassName+'__result'; |
|
const resultClass = tableClass+'__result'; |
|
const resultHeaderClassName = resultClassName+'__header'; |
|
const resultHeaderClass = '.'+resultHeaderClassName; |
|
|
|
// 業種・都道府県のリストを取得 |
|
const jobs = Object.keys(inputData[0]); |
|
jobs.some(function(e, i){ if(e==='index') jobs.splice(i, 1); }); |
|
const dates = inputData.map(function(e){ return e.index; }); |
|
|
|
let jobOrders = []; let dateOrders = []; // 各要素を追加順で管理 |
|
let lastJobOrder = 0; |
|
|
|
// 業種・都道府県のリストタグを生成 |
|
const jobUL = d3.select(jobClass).append('ul'); |
|
const dateUL = d3.select(dateClass).append('ul'); |
|
jobUL.selectAll('li').data(jobs).enter().append('li').attr('class', jobItemClassName).text(function(d){ return d; }); |
|
dateUL.selectAll('li').data(dates).enter().append('li').attr('class', dateItemClassName).text(function(d){ return d; }); |
|
|
|
// 業種・都道府県のリストタグにクリックイベントを付与 |
|
d3.selectAll(jobItemClass).on('click', function(d){ clearTable(resultClass); setChecked(d, this); drawTable(inputData, null); }); |
|
d3.selectAll(dateItemClass).on('click', function(d){ clearTable(resultClass); setChecked(d, this); drawTable(inputData, null); }); |
|
|
|
// チェッククラスを付与・除去する関数 |
|
function setChecked(checkedLi, that){ |
|
const selector = d3.select(that); |
|
if(selector.classed('__checked')) { |
|
if(jobs.indexOf(checkedLi)>-1) jobOrders.some(function(e, i){ if(e===checkedLi) jobOrders.splice(i, 1); }); |
|
else dateOrders.some(function(e, i){ if(e===checkedLi) dateOrders.splice(i, 1); }); |
|
selector.classed('__checked', false); |
|
} else { |
|
if(jobs.indexOf(checkedLi)>-1) jobOrders.push(checkedLi); |
|
else dateOrders.push(checkedLi); |
|
selector.classed('__checked', true); |
|
} |
|
} |
|
|
|
// ソートクラスを付与・除去する関数 |
|
function updateSelected(headers, i, clickedHeader){ |
|
const selector = d3.selectAll(resultHeaderClass); |
|
selector.classed('__selected', false); // 古いクラスをクリア |
|
selector.classed('__selected', function(_,j){ if(i===j) return true; }); |
|
|
|
if(clickedHeader!==null) { |
|
var moveTarget = function(targetList) { |
|
let target = targetList.indexOf(headers[i]); |
|
targetList.splice(target, 1); |
|
targetList.push(headers[i]); |
|
} |
|
if(jobOrders.length>0) moveTarget(jobOrders); |
|
else moveTarget(dateOrders); |
|
} |
|
} |
|
|
|
// チェック済みの要素を検出して、テーブルを描画する関数 |
|
function drawTable(inputData, clickedHeader){ |
|
let checkedJob = []; let checkedDate = []; |
|
d3.selectAll(jobItemClass+'.__checked').attr('_', function(d){ checkedJob.push(d); }); |
|
d3.selectAll(dateItemClass+'.__checked').attr('_', function(d){ checkedDate.push(d); }); |
|
|
|
const table = d3.select(resultClass).append('table'); |
|
|
|
if(checkedJob.length > 0 && checkedDate.length > 0){ |
|
createTable(table, inputData, checkedJob, checkedDate, "job_date", clickedHeader); |
|
} |
|
else if(checkedJob.length > 0 && checkedDate.length === 0){ |
|
createTable(table, inputData, checkedJob, checkedDate, "job", clickedHeader); |
|
} |
|
else if(checkedJob.length === 0 && checkedDate.length > 0){ |
|
createTable(table, inputData, checkedDate, checkedJob, "date", clickedHeader); |
|
} |
|
} |
|
|
|
// テーブルを生成する関数 |
|
function createTable(selector, inputData, headers, indexes, category, clickedHeader){ |
|
const theadText = '<tr><th></th><th class='+resultHeaderClassName+'>' + headers.join('</th><th class='+resultHeaderClassName+'>') + '</tr>'; |
|
selector.append('thead').html(theadText); |
|
|
|
const indexList = (category==='date') ? jobs : dates; |
|
const filterList = (category==='job_date') ? indexes : headers; |
|
const jobList = (category==='date') ? indexes : headers; |
|
|
|
// ソートの対象を設定(追加順が最も新しい要素でソートする) |
|
if(clickedHeader!==null) var sortTarget = clickedHeader; |
|
else if(jobOrders.length>0) var sortTarget = jobOrders[jobOrders.length-1]; |
|
else var sortTarget = dateOrders[dateOrders.length-1]; |
|
var sortOrder = headers.indexOf(sortTarget); |
|
|
|
updateSelected(headers, sortOrder, clickedHeader); |
|
|
|
const shapedData = returnShapedData(inputData, filterList, category); |
|
sortedData(sortTarget, shapedData, indexList); |
|
|
|
const tbody = selector.append('tbody'); |
|
const selectedTrs = tbody.selectAll("tr").exit().remove().data(shapedData); |
|
const newTrs = selectedTrs.enter().append("tr"); |
|
const trs = selectedTrs.merge(newTrs); |
|
|
|
trs.selectAll('td') |
|
.data(function(d){ |
|
let filteredData = headers.map(function(e){ return d[e]; }); |
|
filteredData.unshift(d.index); |
|
return filteredData; |
|
}) |
|
.enter() |
|
.append('td') |
|
.text(function(d){ |
|
return d; |
|
}); |
|
|
|
// 生成したテーブルのヘッダーにクリックイベントを付与 |
|
d3.selectAll(resultHeaderClass) |
|
.on('click', function(_, i){ |
|
const that = this; |
|
clearTable(resultClass); |
|
sortedData(that.textContent, inputData, indexList); |
|
drawTable(inputData, that.textContent); |
|
}); |
|
} |
|
|
|
// テーブルの種類ごとにデータを加工する関数 |
|
function returnShapedData(inputData, filterList, checkedCategory){ |
|
if(checkedCategory==='job') return inputData; |
|
|
|
let filteredData = inputData.filter(function(e){ return (filterList.indexOf(e.index) > -1); }); |
|
if(checkedCategory==='job_date') return filteredData; |
|
|
|
else if(checkedCategory==='date') { |
|
let outputData = Object.keys(filteredData[0]).map(function(key){ |
|
let tempDict = { 'index': key }; |
|
filteredData.forEach(function(e) { tempDict[e.index] = e[key]; }); |
|
return tempDict; |
|
}); |
|
outputData.shift(); |
|
|
|
return outputData; |
|
} |
|
} |
|
|
|
// ソート機能 |
|
function sortedData(targetKey, targetData, targetList){ |
|
targetData.sort(function(a, b) { return (a[targetKey] > b[targetKey]) ? 1 : -1; }); |
|
} |
|
|
|
// クリア機能 |
|
function clearTable(targetClass){ |
|
d3.select(targetClass).select("table").remove(); |
|
} |
|
} |
|
}) |
|
|
|
</script> |
|
</div> |
|
</body> |
|
</html> |