kopia lustrzana https://github.com/learn-awesome/learndb
181 wiersze
4.0 KiB
Svelte
181 wiersze
4.0 KiB
Svelte
<script>
|
|
import * as Pancake from '@sveltejs/pancake';
|
|
import * as d3 from 'd3-hierarchy';
|
|
import { tweened } from 'svelte/motion';
|
|
import * as eases from 'svelte/easing';
|
|
import { fade } from 'svelte/transition';
|
|
import * as yootils from 'yootils';
|
|
import PancakeTreemap from './PancakeTreemap.svelte';
|
|
import topics from '../db/topics.json';
|
|
|
|
const treemap = d3.treemap();
|
|
|
|
const build_hierarchy = (topic_array, parent_name) => {
|
|
// this is different from TopicMasonryGrid because this needs a specific object structure down to all leafs, not a map with just 2 levels
|
|
// every node needs id, name. Leaf nodes need a value like 100. Non-leafs need a children[].
|
|
// see: https://github.com/mekarpeles/math.mx/blob/master/static/data/simple.json
|
|
const nest = (items, id) =>
|
|
items
|
|
.filter(item => item.parent_name === id)
|
|
.map(item => ({ ...item, children: nest(items, item.name), value: 100 }))
|
|
.map(item => {
|
|
return item.children.length == 0 ?
|
|
{id: item.name, name: item.name, value: 100} :
|
|
{id: item.name, name: item.name, children: item.children}
|
|
});
|
|
|
|
let topnodes = nest(topic_array, null); // includes childless nodes
|
|
|
|
// put childless nodes under misc
|
|
let misc = {id: 'misc', name: 'Misc', children: topnodes.filter(t => t.value).slice(0,15)};
|
|
|
|
|
|
return {name: "", children: [...topnodes.filter(t => t.children), misc]};
|
|
}
|
|
|
|
const hierarchy = d3.hierarchy(build_hierarchy(topics, null))
|
|
.sum(d => d.value)
|
|
.sort((a, b) => b.value - a.value)
|
|
|
|
const root = treemap(hierarchy);
|
|
|
|
let selected = root;
|
|
|
|
const select = node => {
|
|
while (node.parent && node.parent !== selected) {
|
|
node = node.parent;
|
|
}
|
|
|
|
if (node && node.children) {
|
|
selected = node;
|
|
} else {
|
|
window.location.href = "/#/topic/" + node.data.name;
|
|
}
|
|
};
|
|
|
|
const breadcrumbs = node => {
|
|
const crumbs = [];
|
|
while (node) {
|
|
crumbs.unshift(node.data.name)
|
|
node = node.parent;
|
|
}
|
|
|
|
return crumbs.join('/');
|
|
};
|
|
|
|
const extents = tweened(undefined, {
|
|
easing: eases.cubicOut,
|
|
duration: 600
|
|
});
|
|
|
|
const is_visible = (a, b) => {
|
|
while (b) {
|
|
if (a.parent === b) return true;
|
|
b = b.parent;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
$: $extents = {
|
|
x1: selected.x0,
|
|
x2: selected.x1,
|
|
y1: selected.y1,
|
|
y2: selected.y0
|
|
};
|
|
</script>
|
|
|
|
<button class="breadcrumbs" disabled="{!selected.parent}" on:click="{() => selected = selected.parent}">
|
|
{breadcrumbs(selected)}
|
|
</button>
|
|
|
|
<div class="chart">
|
|
<Pancake.Chart x1={$extents.x1} x2={$extents.x2} y1={$extents.y1} y2={$extents.y2}>
|
|
<PancakeTreemap {root} let:node>
|
|
{#if is_visible(node, selected)}
|
|
<div
|
|
transition:fade={{duration:400}}
|
|
class="node"
|
|
class:leaf={!node.children}
|
|
on:click="{() => select(node)}"
|
|
>
|
|
<div class="pancontents">
|
|
<strong on:click|stopPropagation={() => window.location.href = "/#/topic/" + node.data.name}>
|
|
{node.data.name.split('/').reverse()[0]}
|
|
</strong>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</PancakeTreemap>
|
|
</Pancake.Chart>
|
|
</div>
|
|
|
|
<style>
|
|
.breadcrumbs {
|
|
width: 100%;
|
|
padding: 0.3rem 0.4rem;
|
|
background-color: transparent;
|
|
font-family: inherit;
|
|
font-size: inherit;
|
|
text-align: left;
|
|
border: none;
|
|
cursor: pointer;
|
|
outline: none;
|
|
}
|
|
|
|
.breadcrumbs:disabled {
|
|
cursor: default;
|
|
}
|
|
|
|
.chart {
|
|
width: calc(100% + 2px);
|
|
height: 900px;
|
|
padding: 0;
|
|
margin: 0 -1px 36px -1px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.node {
|
|
position: absolute;
|
|
width: 100%;
|
|
height: 100%;
|
|
background-color: white;
|
|
overflow: hidden;
|
|
pointer-events: all;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.pancontents {
|
|
width: 100%;
|
|
height: 100%;
|
|
padding: 0.3rem 0.4rem;
|
|
border: 1px solid white;
|
|
background-color: hsl(240, 8%, 70%);
|
|
color: white;
|
|
border-radius: 4px;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.pancontents:hover {
|
|
background-color: #F1EB9A;
|
|
color: black;
|
|
}
|
|
|
|
.node:not(.leaf) .pancontents {
|
|
background-color: hsl(240, 8%, 44%);
|
|
}
|
|
|
|
.node:not(.leaf) .pancontents:hover {
|
|
background-color: #F1EB9A;
|
|
}
|
|
|
|
strong {
|
|
display: block;
|
|
font-size: 16px;
|
|
white-space: nowrap;
|
|
line-height: 1;
|
|
text-decoration: underline;
|
|
}
|
|
|
|
|
|
</style> |