Skip to content

Instantly share code, notes, and snippets.

@motsu0
Last active January 2, 2022 07:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save motsu0/7a7c62fd9ddcb2be4c8244573c1a5a1d to your computer and use it in GitHub Desktop.
Save motsu0/7a7c62fd9ddcb2be4c8244573c1a5a1d to your computer and use it in GitHub Desktop.
#canvas{
width: 100%;
border: 1px solid #ddd;
}
.sub-area{
box-sizing: border-box;
margin: 20px 0;
border: 2px solid #aaa;
}
.sub-title{
margin: 4px;
border-bottom: 1px solid #aaa;
text-align: center;
}
summary{
display: block;
cursor: pointer;
user-select: none;
}
summary::before{
content: "+";
display: inline-flex;
justify-content: center;
align-items: center;
width: 20px;
}
details[open]>summary::before{
content: "-";
}
.sub-body{
margin: 4px 8px;
}
.input-data{
width: 60px;
margin: 0 4px;
}
.output,.output-title{
border-color: #555;
}
.sel-data{
margin: 0 4px;
cursor: pointer;
}
#bt-dl{
display: block;
padding: 4px 8px;
margin: 20px auto;
cursor: pointer;
}
#bt-reset-pos{
display: block;
margin-left: auto;
cursor: pointer;
}
/* */
#msg-box{
box-sizing: border-box;
width: 100%;
position: fixed;
bottom: 10px;
left: 0;
z-index: 1000;
text-align: center;
pointer-events: none;
}
#message{
display: inline-block;
box-sizing: border-box;
max-width: 90%;
padding: 10px 20px;
border: 2px solid #BF360C;
background-color: #FBE9E7;
color: #BF360C;
word-break: break-all;
opacity: 1;
pointer-events: auto;
}
#message.off{
opacity: 0;
transition: opacity 1s;
pointer-events: none;
}
.invisivle{
visibility: hidden;
}
@media screen and (min-width:769px) {
#main-area{
width: 60%;
margin: 0 auto;
}
}
<div id="main-area">
<div id="draw-box">
<canvas id="canvas" width="600px" height="600px"></canvas>
</div>
<button id="bt-dl">画像をダウンロード</button>
<details class="input-area sub-area" open>
<summary class="input-title sub-title">設定</summary>
<div class="input-row sub-body">
a:<input type="number" class="input-len input-data" min="1" placeholder="3" value="3">
</div>
<div class="input-row sub-body">
b:<input type="number" class="input-len input-data" min="1" placeholder="4" value="4">
</div>
<div class="input-row sub-body">
c:<input type="number" class="input-len input-data" min="1" placeholder="5" value="5">
</div>
<div class="input-row sub-body">
回転角度:<input type="number" id="input-angle" class="input-data" placeholder="0" value="0">°
</div>
<div class="input-row sub-body">
背景色:<select id="sel-bg" class="sel-data">
<option value="#fff">白色</option>
<option value="rgba(0,0,0,0)">透明</option>
</select>
</div>
<div class="input-row sub-body">
<select id="sel-angle" class="sel-data">
<option value="on">角の文字(ABC)を表示</option>
<option value="off">角の文字(ABC)を非表示</option>
</select>
</div>
<div class="input-row sub-body">
<select id="sel-edge" class="sel-data">
<option value="on">辺の文字(abc)を表示</option>
<option value="num">辺の値を表示</option>
<option value="off">辺の文字(abc)と値を非表示</option>
</select>
</div>
</details>
<details class="sub-area" open>
<summary class="sub-title">文字の位置調整</summary>
<div class="input-row sub-body">
A
 x:<input type="number" class="input-data input-pos-x" value="0" placeholder="0">
 y:<input type="number" class="input-data input-pos-y" value="0" placeholder="0">
</div>
<div class="input-row sub-body">
B
 x:<input type="number" class="input-data input-pos-x" value="0" placeholder="0">
 y:<input type="number" class="input-data input-pos-y" value="0" placeholder="0">
</div>
<div class="input-row sub-body">
C
 x:<input type="number" class="input-data input-pos-x" value="0" placeholder="0">
 y:<input type="number" class="input-data input-pos-y" value="0" placeholder="0">
</div>
<div class="input-row sub-body">
a
 x:<input type="number" class="input-data input-pos-x" value="0" placeholder="0">
 y:<input type="number" class="input-data input-pos-y" value="0" placeholder="0">
</div>
<div class="input-row sub-body">
b
 x:<input type="number" class="input-data input-pos-x" value="0" placeholder="0">
 y:<input type="number" class="input-data input-pos-y" value="0" placeholder="0">
</div>
<div class="input-row sub-body">
c
 x:<input type="number" class="input-data input-pos-x" value="0" placeholder="0">
 y:<input type="number" class="input-data input-pos-y" value="0" placeholder="0">
</div>
<div class="input-row sub-body">
<button id="bt-reset-pos">リセット</button>
</div>
</details>
<div class="output sub-area">
<div class="output-title sub-title">
三角形の情報<br>
※各値は小数十一位を四捨五入したもの
</div>
<div id="output-body" class="sub-body"></div>
</div>
</div>
<div id="msg-box">
<div id="message" class="off">データを保存しました。</div>
</div>
const input_data = document.getElementsByClassName('input-data');
const input_len = document.getElementsByClassName('input-len');
const input_angle = document.getElementById('input-angle');
const sel_data = document.getElementsByClassName('sel-data');
const sel_bg = document.getElementById('sel-bg');
const sel_angle = document.getElementById('sel-angle');
const sel_edge = document.getElementById('sel-edge');
const input_pos_x = document.getElementsByClassName('input-pos-x');
const input_pos_y = document.getElementsByClassName('input-pos-y');
const bt_reset_pos = document.getElementById('bt-reset-pos');
const bt_dl = document.getElementById('bt-dl');
const output_body = document.getElementById('output-body');
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const fontsize = 30;
const delta_angle = 25;
const delta_edge = 20;
const can_wh = 500;
const can_margin = 50;
ctx.font = fontsize + 'px serif';
ctx.textAlign = 'center';
[...input_data].forEach(el=>{
el.addEventListener('input',draw);
});
[...sel_data].forEach(el=>{
el.addEventListener('change',draw);
});
bt_dl.addEventListener('click',download);
bt_reset_pos.addEventListener('click',resetPos);
draw();
function draw(){
//例外処理
//辺の長さ取得
const abc = [...input_len].map(el=>Number(el.value));
for(let i=0;i<input_len.length;i++){
if(input_len[i].value==''){
renew();
return;
}
}
//入力数値例外処理
for(let i=0;i<=2;i++){
if(isNaN(abc[i])||abc[i]<=0){
renew();
msgBox('入力値に誤りがあります。');
return;
}
}
//位置調整取得 例外処理
const pos_x = [...input_pos_x].map(el=>Number(el.value));
const pos_y = [...input_pos_y].map(el=>Number(el.value));
for(let i=0;i<pos_x.length;i++){
if(isNaN(pos_x[i])||isNaN(pos_y[i])){
renew();
msgBox('入力値に誤りがあります。');
return;
}
}
//回転角度取得 例外処理
const degree = Number(input_angle.value);
if(isNaN(degree)){
renew();
msgBox('入力値に誤りがあります。');
return;
}
const alpha = degree*Math.PI/180;
//三角形の成立判定
const abc_sort = abc.slice(0).sort((a,b)=>a-b);
if(abc_sort[0]+abc_sort[1]<=abc_sort[2]){
renew();
msgBox('三角形の条件を満たしていません。');
return;
}
//外接円半径Rと拡大率
const R = can_wh/2;
const kariR = (abc[0]*abc[1]*abc[2]) / Math.sqrt((abc[0]+abc[1]+abc[2])*(-abc[0]+abc[1]+abc[2])*(abc[0]-abc[1]+abc[2])*(abc[0]+abc[1]-abc[2]));
const rate = R / kariR;
//abcの長さ
const a = abc[0] * rate;
const b = abc[1] * rate;
const c = abc[2] * rate;
//Aを原点とした点C、点P(外心)の座標
const Bx = (-Math.pow(a,2)+Math.pow(b,2)+Math.pow(c,2)) / (2*b);
const By = Math.sqrt(Math.pow(c,2) - Math.pow(((-Math.pow(a,2)+Math.pow(b,2)+Math.pow(c,2)) / (2*b)),2));
const Px = b/2;
const Py = Math.sqrt(Math.pow(R,2)-Math.pow(b,2)/4);
//平行移動後の点Pの座標 canvasの中心
const Ox = can_wh/2 + can_margin;
const Oy = Ox;
//点Pをcanvas中心とした際の点A-Cの座標
const kariA = [-Px, -Py];
const kariB = [Bx-Px, By-Py];
const kariC = [b-Px, -Py];
//回転
const rotA = rotate(kariA,alpha);
const rotB = rotate(kariB,alpha);
const rotC = rotate(kariC,alpha);
//座標軸を通常のxy軸からcanvas座標軸に変更
const canA = [rotA[0],-rotA[1]];
const canB = [rotB[0],-rotB[1]];
const canC = [rotC[0],-rotC[1]];
//点Pに合うように平行移動
const A = [Ox+canA[0],Oy+canA[1]];
const B = [Ox+canB[0],Oy+canB[1]];
const C = [Ox+canC[0],Oy+canC[1]];
//描画処理
//初期化
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = sel_bg.value;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#000';
//内心を求める
const I = [(a*A[0]+b*B[0]+c*C[0])/(a+b+c),(a*A[1]+b*B[1]+c*C[1])/(a+b+c)];
//角の文字の座標を求め、描画
const letter_angle = ['A','B','C'];
const zahyou_angle = [A,B,C];
if(sel_angle.value=='on'){
for(let i=0;i<=2;i++){
const sin = (zahyou_angle[i][1]-I[1])/(Math.sqrt(Math.pow(zahyou_angle[i][0]-I[0],2)+Math.pow(zahyou_angle[i][1]-I[1],2)));
const cos = (zahyou_angle[i][0]-I[0])/(Math.sqrt(Math.pow(zahyou_angle[i][0]-I[0],2)+Math.pow(zahyou_angle[i][1]-I[1],2)));
const zahyou = [zahyou_angle[i][0]+delta_angle*cos, zahyou_angle[i][1]+delta_angle*sin];
const adjust = [pos_x[i], fontsize/2-pos_y[i]];
ctx.fillText(letter_angle[i],zahyou[0]+adjust[0],zahyou[1]+adjust[1]);
}
}
//辺の文字の座標を求め、描画
if(sel_edge.value=='on'||sel_edge.value=='num'){
const letter_edge = (()=>{
if(sel_edge.value=='on'){
return ['a','b','c'];
}else{
return abc.slice(0);
}
})();
const zahyou_edge = [
[(B[0]+C[0])/2,(B[1]+C[1])/2],
[(C[0]+A[0])/2,(C[1]+A[1])/2],
[(A[0]+B[0])/2,(A[1]+B[1])/2]
];
const info_edge = [
[(B[1]-C[1])/(B[0]-C[0]), B],
[(C[1]-A[1])/(C[0]-A[0]), C],
[(A[1]-B[1])/(A[0]-B[0]), A]
];
for(let i=0;i<=2;i++){
const hosei = new Array(2);
const atan = Math.atan(info_edge[i][0]);
hosei[0] = delta_edge * Math.cos(atan-Math.PI/2);
hosei[1] = delta_edge * Math.sin(atan-Math.PI/2);
const dist = info_edge[i][0]*(zahyou_angle[i][0] - info_edge[i][1][0]) + info_edge[i][1][1];
if(dist>zahyou_angle[i][1]){
hosei[0] *= -1;
hosei[1] *= -1;
}
const zahyou = [zahyou_edge[i][0]+hosei[0], zahyou_edge[i][1]+hosei[1]];
const hosei_fs_x = (()=>{
if(sel_edge.value!='num'){
return 0
}else if(hosei[0]>=0){
return (String(abc[i]).length-1)*fontsize*.5*.5;
}else{
return -(String(abc[i]).length-1)*fontsize*.5*.5;
}
})();
const adjust = [hosei_fs_x+pos_x[i+3], fontsize*.5-pos_y[i+3]];
ctx.fillText(letter_edge[i],zahyou[0]+adjust[0],zahyou[1]+adjust[1]);
}
}
//三角形を描画
ctx.beginPath();
ctx.moveTo(A[0],A[1]);
ctx.lineTo(B[0],B[1]);
ctx.lineTo(C[0],C[1]);
ctx.lineTo(A[0],A[1]);
ctx.stroke();
//三角形の情報
const info = [];
info.push(`a:${abc[0]}`);
info.push(`b:${abc[1]}`);
info.push(`c:${abc[2]}`);
info.push(`回転角度:${Number(input_angle.value)}°`);
const cosA = (Math.pow(abc[1],2)+Math.pow(abc[2],2)-Math.pow(abc[0],2)) / (2*abc[1]*abc[2]);
const cosB = (Math.pow(abc[2],2)+Math.pow(abc[0],2)-Math.pow(abc[1],2)) / (2*abc[2]*abc[0]);
const cosC = (Math.pow(abc[0],2)+Math.pow(abc[1],2)-Math.pow(abc[2],2)) / (2*abc[0]*abc[1]);
info.push(`cosA:${round(cosA)}`);
info.push(`cosB:${round(cosB)}`);
info.push(`cosC:${round(cosC)}`);
info.push(`sinA:${round(Math.sqrt(1-Math.pow(cosA,2)))}`);
info.push(`sinB:${round(Math.sqrt(1-Math.pow(cosB,2)))}`);
info.push(`sinC:${round(Math.sqrt(1-Math.pow(cosC,2)))}`);
const area = Math.sqrt(((abc[0]+abc[1]+abc[2])/2)*((abc[0]+abc[1]+abc[2])/2-abc[0])*((abc[0]+abc[1]+abc[2])/2-abc[1])*((abc[0]+abc[1]+abc[2])/2-abc[2]));
info.push(`面積:${round(area)}`);
output_body.innerHTML = info.join('<br>');
//処理終了
function rotate(origin,angle){
const x = R * ( (origin[0]/R)*Math.cos(angle) + (origin[1]/R)*Math.sin(angle) );
const y = R * ( (origin[1]/R)*Math.cos(angle) - (origin[0]/R)*Math.sin(angle) );
return [x,y];
}
function renew(){
ctx.clearRect(0, 0, canvas.width, canvas.height);
output_body.textContent = '';
}
}
function download(){
const alink = document.createElement('a');
alink.download = 'triangle.png';
canvas.toBlob(blob=>{
alink.href = window.URL.createObjectURL(blob);
alink.click();
},'image/png');
}
function resetPos(){
[...input_pos_x].forEach(el=>{
el.value = 0;
});
[...input_pos_y].forEach(el=>{
el.value = 0;
});
draw();
}
function round(n){
return Math.round(n*Math.pow(10,10)) / Math.pow(10,10);
}
function msgBox(str){
message.textContent = str;
message.classList.remove('off');
setTimeout(()=>{
message.classList.add('off');
},1000);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment