1
0
mirror of https://github.com/chanind/hanzi-writer-data.git synced 2025-07-19 15:28:54 +08:00

adding medians display option to data visualizer

This commit is contained in:
David Chanin 2019-06-12 23:23:34 +01:00
parent dd33af7831
commit 153e84df9f
3 changed files with 225 additions and 144 deletions

View File

@ -1,12 +1,13 @@
var VERSION = '2.0'; var VERSION = "2.0";
var getCharDataUrl = (char) => `https://cdn.jsdelivr.net/npm/hanzi-writer-data@${VERSION}/${char}.json`; var getCharDataUrl = char =>
`https://cdn.jsdelivr.net/npm/hanzi-writer-data@${VERSION}/${char}.json`;
function loadData(char, onLoad, onError) { function loadData(char, onLoad, onError) {
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
if (xhr.overrideMimeType) { if (xhr.overrideMimeType) {
xhr.overrideMimeType('application/json'); xhr.overrideMimeType("application/json");
} }
xhr.open('GET', getCharDataUrl(char), true); xhr.open("GET", getCharDataUrl(char), true);
xhr.onreadystatechange = () => { xhr.onreadystatechange = () => {
if (xhr.readyState !== 4) return; if (xhr.readyState !== 4) return;
if (xhr.status === 200) { if (xhr.status === 200) {
@ -16,67 +17,93 @@ function loadData(char, onLoad, onError) {
} }
}; };
xhr.send(null); xhr.send(null);
}; }
function attr(elm, name, value) { function attr(elm, name, value) {
elm.setAttributeNS(null, name, value); elm.setAttributeNS(null, name, value);
} }
function createElm(elmType) { function createElm(elmType) {
return document.createElementNS('http://www.w3.org/2000/svg', elmType); return document.createElementNS("http://www.w3.org/2000/svg", elmType);
}
function getPathString(points) {
const start = points[0];
const remainingPoints = points.slice(1);
let pathString = `M ${start[0]} ${start[1]}`;
remainingPoints.forEach(point => {
pathString += ` L ${point[0]} ${point[1]}`;
});
return pathString;
} }
function renderCharacter(charData) { function renderCharacter(charData) {
var target = document.querySelector('#target'); var target = document.querySelector("#target");
document.body.classList.toggle('has-radical-data', !!charData.radStrokes); document.body.classList.toggle("has-radical-data", !!charData.radStrokes);
target.innerHTML = ''; target.innerHTML = "";
var svg = createElm('svg'); var svg = createElm("svg");
attr(svg, 'width', '100%'); attr(svg, "width", "100%");
attr(svg, 'height', '100%'); attr(svg, "height", "100%");
target.appendChild(svg); target.appendChild(svg);
var group = createElm('g'); var group = createElm("g");
attr(group, 'transform', 'translate(0, 263.671875) scale(0.29296875, -0.29296875)'); attr(
group,
"transform",
"translate(0, 263.671875) scale(0.29296875, -0.29296875)"
);
svg.appendChild(group); svg.appendChild(group);
charData.strokes.forEach((stroke, i) => { charData.strokes.forEach((stroke, i) => {
var isRadical = (charData.radStrokes || []).indexOf(i) > -1; var isRadical = (charData.radStrokes || []).indexOf(i) > -1;
var path = createElm('path'); var path = createElm("path");
attr(path, 'd', stroke); attr(path, "d", stroke);
path.classList.toggle('radical-stroke', isRadical); path.classList.toggle("radical-stroke", isRadical);
group.appendChild(path);
});
charData.medians.forEach(median => {
var path = createElm("path");
attr(path, "d", getPathString(median));
path.classList.add("median-stroke");
group.appendChild(path); group.appendChild(path);
}); });
} }
function updateClasses() { function updateClasses() {
var target = document.querySelector('#target'); var target = document.querySelector("#target");
var transparent = document.querySelector('#transparent').checked; var transparent = document.querySelector("#transparent").checked;
var radical = document.querySelector('#radical').checked; var radical = document.querySelector("#radical").checked;
target.classList.toggle('transparent-strokes', transparent); var medians = document.querySelector("#medians").checked;
target.classList.toggle('color-radical', radical); target.classList.toggle("transparent-strokes", transparent);
target.classList.toggle("color-radical", radical);
target.classList.toggle("show-medians", medians);
} }
document.querySelectorAll('input[type=checkbox]').forEach(function(node) { document.querySelectorAll("input[type=checkbox]").forEach(function(node) {
node.addEventListener('change', updateClasses); node.addEventListener("change", updateClasses);
}); });
function renderLoadPath(char) { function renderLoadPath(char) {
var cdnTarget = document.querySelector('#cdn-path'); var cdnTarget = document.querySelector("#cdn-path");
var url = getCharDataUrl(char) var url = getCharDataUrl(char);
cdnTarget.innerHTML = `<a href="${url}" target="blank">${url}</a>`; cdnTarget.innerHTML = `<a href="${url}" target="blank">${url}</a>`;
var npmTarget = document.querySelector('#npm-path'); var npmTarget = document.querySelector("#npm-path");
npmTarget.innerHTML = `var charData = require('hanzi-writer-data/${char}.json');`; npmTarget.innerHTML = `var charData = require('hanzi-writer-data/${char}.json');`;
} }
function loadAndRender() { function loadAndRender() {
var char = document.querySelector('#char-input').value; var char = document.querySelector("#char-input").value;
loadData(char, function(charData) { loadData(
renderCharacter(charData); char,
renderLoadPath(char); function(charData) {
window.location.hash = char.codePointAt(0); renderCharacter(charData);
}, function(err) { renderLoadPath(char);
console.error(err); window.location.hash = char.codePointAt(0);
alert(`Unable to load data for ${char}`); },
}); function(err) {
console.error(err);
alert(`Unable to load data for ${char}`);
}
);
} }
function setCharFromHash(defaultChar) { function setCharFromHash(defaultChar) {
@ -88,16 +115,15 @@ function setCharFromHash(defaultChar) {
} }
} }
if (hashChar) { if (hashChar) {
document.querySelector('#char-input').value = hashChar; document.querySelector("#char-input").value = hashChar;
} }
} }
setCharFromHash('我'); setCharFromHash("我");
window.addEventListener('hashchange', function() { window.addEventListener("hashchange", function() {
setCharFromHash(null); setCharFromHash(null);
loadAndRender(); loadAndRender();
}); });
document.querySelector('#update-char').addEventListener('click', loadAndRender); document.querySelector("#update-char").addEventListener("click", loadAndRender);
updateClasses(); updateClasses();
loadAndRender(); loadAndRender();

View File

@ -1,46 +1,82 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en-us"> <html lang="en-us">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8" />
<title>Hanzi Writer Data Explorer</title> <title>Hanzi Writer Data Explorer</title>
<link rel="stylesheet" href="styles.css" /> <link rel="stylesheet" href="styles.css" />
</head> </head>
<body> <body>
<h1 class="title">Hanzi Writer Data Explorer</h1> <h1 class="title">Hanzi Writer Data Explorer</h1>
<p class="description"> <p class="description">
Explore the Chinese character SVG data used by <a href="https://chanind.github.io/hanzi-writer/">Hanzi Writer</a>. These stroke paths come from the <a href="https://github.com/skishore/makemeahanzi">Make me a Hanzi</a> project. Explore the Chinese character SVG data used by
</p> <a href="https://chanind.github.io/hanzi-writer/">Hanzi Writer</a>. These
stroke paths come from the
<a href="https://github.com/skishore/makemeahanzi">Make me a Hanzi</a>
project.
</p>
<div class="controls char-input-controls"> <div class="controls char-input-controls">
<input type="text" class="char-input" id="char-input" size="1" maxlength="1" /> <input
<button type="submit" id="update-char">Update</button> type="text"
</div> class="char-input"
id="char-input"
size="1"
maxlength="1"
/>
<button type="submit" id="update-char">Update</button>
</div>
<div id="target"> <div id="target"></div>
<div class="controls">
<label>
<input
type="checkbox"
checked
name="transparent"
id="transparent"
value="1"
/>
Transparent strokes
</label>
<label class="radical-control">
<input type="checkbox" checked name="radical" id="radical" value="1" />
Color radical
</label>
<label>
<input type="checkbox" name="medians" id="medians" value="1" />
Show medians
</label>
</div>
</div> <div class="do-it">Load this data via CDN</div>
<div class="controls"> <div class="cdn-path" id="cdn-path"></div>
<label> <div>Or via NPM</div>
<input type="checkbox" checked name="transparent" id="transparent" value="1" /> <div class="npm-path" id="npm-path"></div>
Transparent strokes
</label>
<label class="radical-control">
<input type="checkbox" checked name="radical" id="radical" value="1" />
Color radical
</label>
</div>
<div class="do-it">Load this data via CDN</div> <div class="license">
<div class="cdn-path" id="cdn-path"></div> The Character data Hanzi Writer uses is from the
<div>Or via NPM</div> <a href="https://github.com/skishore/makemeahanzi">Make Me A Hanzi</a>
<div class="npm-path" id="npm-path"></div> project, which extracted the data from fonts by
<a href="http://www.arphic.com/">Arphic Technology</a>, a Taiwanese font
forge that released their work under a permissive license in 1999. You can
redistribute and/or modify this data (in the
<a href="https://github.com/chanind/hanzi-writer-data"
>hanzi-writer-data</a
>
github repository) under the terms of the
<a href="http://ftp.gnu.org/non-gnu/chinese-fonts-truetype/LICENSE"
>Arphic Public License</a
>
as published by Arphic Technology Co., Ltd. A copy of this license can
also be found in
<a
href="https://raw.githubusercontent.com/chanind/hanzi-writer-data/master/ARPHICPL.TXT"
>ARPHICPL.TXT</a
>
of the hanzi-writer-data repo.
</div>
<div class="license"> <script type="application/javascript" src="explorer.js"></script>
The Character data Hanzi Writer uses is from the <a href="https://github.com/skishore/makemeahanzi">Make Me A Hanzi</a> project, which extracted the data from fonts by <a href="http://www.arphic.com/">Arphic Technology</a>, a Taiwanese font forge that released their work under a permissive license in 1999. You can redistribute and/or modify this data (in the <a href="https://github.com/chanind/hanzi-writer-data">hanzi-writer-data</a> github repository) under the terms of the <a href="http://ftp.gnu.org/non-gnu/chinese-fonts-truetype/LICENSE">Arphic Public License</a> as published by Arphic Technology Co., Ltd. A copy of this license can also be found in <a href="https://raw.githubusercontent.com/chanind/hanzi-writer-data/master/ARPHICPL.TXT">ARPHICPL.TXT</a> of the hanzi-writer-data repo. </body>
</div> </html>
<script type="application/javascript" src="explorer.js"></script>
</body>
</html>

View File

@ -1,123 +1,142 @@
body, html { body,
background: #f4f0ea; html {
margin: 0; background: #f4f0ea;
padding: 0; margin: 0;
text-align: center; padding: 0;
font-size: 14px; text-align: center;
font-family: sans-serif; font-size: 14px;
font-family: sans-serif;
} }
body { body {
padding-bottom: 80px; padding-bottom: 80px;
} }
* { * {
box-sizing: border-box; box-sizing: border-box;
} }
input, button { input,
outline: none; button {
outline: none;
} }
#target, .title, .controls, .cdn-path, .npm-path { #target,
box-shadow: 0 1px 5px rgba(0,0,0,0.15); .title,
.controls,
.cdn-path,
.npm-path {
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.15);
} }
.description { .description {
max-width: 400px; max-width: 400px;
margin: 30px auto 50px; margin: 30px auto 50px;
line-height: 25px; line-height: 25px;
} }
.title { .title {
background: white; background: white;
padding: 15px 0; padding: 15px 0;
margin: 0 0 30px; margin: 0 0 30px;
} }
#target { #target {
background: white; background: white;
clear: both; clear: both;
border: 1px solid black; border: 1px solid black;
width: 300px; width: 300px;
height: 300px; height: 300px;
margin: 0 auto; margin: 0 auto;
} }
.controls { .controls {
width: 300px; width: 300px;
padding: 5px; padding: 5px;
background: white; background: white;
border: 1px solid black; border: 1px solid black;
margin: 5px auto; margin: 5px auto;
text-align: left; text-align: left;
} }
.char-input-controls { .char-input-controls {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
} }
.char-input { .char-input {
padding: 5px; padding: 5px;
margin-right: 5px; margin-right: 5px;
font-size: 24px; font-size: 24px;
display: block; display: block;
flex-grow: 1; flex-grow: 1;
} }
.do-it { .do-it {
margin-top: 50px; margin-top: 50px;
} }
.cdn-path, .npm-path { .cdn-path,
max-width: 600px; .npm-path {
background: white; max-width: 600px;
padding: 10px 30px; background: white;
margin: 20px auto 40px; padding: 10px 30px;
display: inline-block; margin: 20px auto 40px;
user-select: all; display: inline-block;
user-select: all;
} }
.npm-path { .npm-path {
font-family: monospace; font-family: monospace;
} }
.license { .license {
font-size: 12px; font-size: 12px;
max-width: 600px; max-width: 600px;
margin: 50px auto 0; margin: 50px auto 0;
} }
.license, .license a { .license,
color: #999; .license a {
text-shadow: 1px 1px 0 white; color: #999;
text-shadow: 1px 1px 0 white;
} }
.controls label { .controls label {
display: block; display: block;
margin: 5px 0; margin: 5px 0;
} }
path { path {
fill: #555; fill: #555;
stroke: #000; stroke: #000;
stroke-width: 0; stroke-width: 0;
transition: all 500ms; transition: all 500ms;
} }
.transparent-strokes path { .transparent-strokes path {
fill: rgba(0,0,0,0.4); fill: rgba(0, 0, 0, 0.4);
stroke-width: 2px; stroke-width: 2px;
}
path.median-stroke {
fill: none;
stroke: rgba(0, 0, 0, 0.4);
stroke-width: 3px;
opacity: 0;
}
.show-medians path.median-stroke {
opacity: 1;
} }
.radical-control { .radical-control {
color: #AAA; color: #aaa;
} }
.has-radical-data .radical-control { .has-radical-data .radical-control {
color: initial; color: initial;
} }
.color-radical .radical-stroke { .color-radical .radical-stroke {
fill: rgb(22, 110, 22); fill: rgb(22, 110, 22);
} }
.transparent-strokes.color-radical .radical-stroke { .transparent-strokes.color-radical .radical-stroke {
fill: rgba(22, 110, 22, 0.4); fill: rgba(22, 110, 22, 0.4);
} }