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:
parent
dd33af7831
commit
153e84df9f
108
explorer.js
108
explorer.js
@ -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();
|
||||||
|
|
||||||
|
112
index.html
112
index.html
@ -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>
|
|
||||||
|
149
styles.css
149
styles.css
@ -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);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user