- 投稿日:2020-04-30T23:41:30+09:00
(フロントエンド初心者)AngularのPWA少しずつちゃんとやる。デプロイしたら端末側は最新状態に画面をアップデートしたい(Todoistみたいに)
PWA簡単にできますよっていうQiitaの記事はみましたが、本当かな。
PWAが一度有効になるとServiceWorkerから情報取るのでデプロイで新しいソースが来ても端末側で更新できない問題がすぐに見つかる。どうすんの。
Todoistとかはこの辺うまくやってるのですごいなぁと思う。
公式ドキュメントは日本語よみにくいことこの上なしこの辺の情報は日本語でググっても情報なかったので私がググった結果をリンクおいておきます
結論
下記を実装しましょう
https://medium.com/@arjenbrandenburgh/angulars-pwa-swpush-and-swupdate-15a7e5c154ac 記事のGithubと、updateがうまく行かないので下記を
実装しましょう。
動作確認
- Chrome
- iPhoneでホーム画面に追加した画面
動作確認できました。Edgeとか知らん。Firefoxはしらん。
このあと試したいこと
オフライン <=> オンラインを行き来してうまく端末が追従するか →できたっぽい。
追記
下記シナリオで端末が最新に更新できることを確認しました。
- 6秒おきにサーバーに確認するようにプログラム(記事は6時間)
- iPhoneのホーム画面に設置したAngularをオンラインで表示
- iPhoneを機内モード(オフライン)
- Angular(FirebaseのHosting)をちょっとだけ更新してデプロイ
- iPhoneの機内モード解除(オンライン)
- しばらくしたらiPhoneにて更新がかかる
良い感じ
- 投稿日:2020-04-30T23:30:08+09:00
Reactチュートリアルをやってみて
■ やってみたこと
- Reactチュートリアル
- 五目並べを作りつながらReactを学んでいくチュートリアル
- チュートリアルの最終結果 に少し手を加えた。
- 各componentをファイルに分割
- React Hooks を使うように変更
- Jest, Enzymeでテストを実装
- コードはここに置いてあります https://github.com/ckona/tic-tac-toe-with-react
■ チュートリアルの最終結果
function Square(props) { return ( <button className="square" onClick={props.onClick}> {props.value} </button> ); } class Board extends React.Component { renderSquare(i) { return ( <Square value={this.props.squares[i]} onClick={() => this.props.onClick(i)} /> ); } render() { return ( <div> <div className="board-row"> {this.renderSquare(0)} {this.renderSquare(1)} {this.renderSquare(2)} </div> <div className="board-row"> {this.renderSquare(3)} {this.renderSquare(4)} {this.renderSquare(5)} </div> <div className="board-row"> {this.renderSquare(6)} {this.renderSquare(7)} {this.renderSquare(8)} </div> </div> ); } } class Game extends React.Component { constructor(props) { super(props); this.state = { history: [ { squares: Array(9).fill(null) } ], stepNumber: 0, xIsNext: true }; } handleClick(i) { const history = this.state.history.slice(0, this.state.stepNumber + 1); const current = history[history.length - 1]; const squares = current.squares.slice(); if (calculateWinner(squares) || squares[i]) { return; } squares[i] = this.state.xIsNext ? "X" : "O"; this.setState({ history: history.concat([ { squares: squares } ]), stepNumber: history.length, xIsNext: !this.state.xIsNext }); } jumpTo(step) { this.setState({ stepNumber: step, xIsNext: (step % 2) === 0 }); } render() { const history = this.state.history; const current = history[this.state.stepNumber]; const winner = calculateWinner(current.squares); const moves = history.map((step, move) => { const desc = move ? 'Go to move #' + move : 'Go to game start'; return ( <li key={move}> <button onClick={() => this.jumpTo(move)}>{desc}</button> </li> ); }); let status; if (winner) { status = "Winner: " + winner; } else { status = "Next player: " + (this.state.xIsNext ? "X" : "O"); } return ( <div className="game"> <div className="game-board"> <Board squares={current.squares} onClick={i => this.handleClick(i)} /> </div> <div className="game-info"> <div>{status}</div> <ol>{moves}</ol> </div> </div> ); } } // ======================================== ReactDOM.render(<Game />, document.getElementById("root")); function calculateWinner(squares) { const lines = [ [0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6] ]; for (let i = 0; i < lines.length; i++) { const [a, b, c] = lines[i]; if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { return squares[a]; } } return null; }■ 最終的にファイル分割して、React Hooks を使うようにしたVer
/src/index.jsをGameコンポーネントの呼び出しをするだけに。/src/components/ディレクトリを切り、そこに各コンポーネントを移動する。- classコンポーネントをやめて、React Hooks を使ってみる
- thisが無くなるので見やすくなりますね。
index.js
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import Game from './components/Game'; ReactDOM.render( <Game />, document.getElementById('root') );Square
import React from 'react'; const Square = props => ( <button className="square" onClick={props.onClick}> {props.value} </button> ); export default Square;Board
import React from 'react'; import Square from './Square'; const Board = props => { const renderSquare = i => ( <Square value={props.squares[i]} onClick={() => props.onClick(i)} /> ); return ( <div> <div className="board-row"> {renderSquare(0)} {renderSquare(1)} {renderSquare(2)} </div> <div className="board-row"> {renderSquare(3)} {renderSquare(4)} {renderSquare(5)} </div> <div className="board-row"> {renderSquare(6)} {renderSquare(7)} {renderSquare(8)} </div> </div> ); }; export default Board;Game
import React, { useState } from 'react'; import Board from './Board'; const Game = () => { const [history, setHistory] = useState([{squares: Array(9).fill(null)}]); const [stepNumber, setStepNumber] = useState(0); const [xIsNext, setXIsNext] = useState(true); const handleClick = i => { const _history = history.slice(0, stepNumber + 1); const current = _history[_history.length - 1]; const squares = current.squares.slice(); if (calculateWinner(squares) || squares[i]) { return; } squares[i] = xIsNext ? 'X' : 'O'; setHistory(_history.concat([{squares: squares,}])); setStepNumber(_history.length); setXIsNext(!xIsNext); }; const jumpTo = step => { setStepNumber(step); setXIsNext((step % 2) === 0); }; const current = history[stepNumber]; const moves = history.map((step, move) => { const desc = move ? 'Go to move #' + move : 'Go to game start'; return ( <li key={move}> <button onClick={() => jumpTo(move)}>{desc}</button> </li> ); }); const status = () => { const winner = calculateWinner(current.squares); if (winner) { return 'Winner: ' + winner; } else { return 'Next player: ' + (xIsNext ? 'X' : 'O'); } }; const calculateWinner = squares => { const lines = [ [0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6] ]; for (let i = 0; i < lines.length; i++) { const [a, b, c] = lines[i]; if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { return squares[a]; } } return null; }; return ( <div className="game"> <div className="game-board"> <Board squares={current.squares} onClick={(i) => handleClick(i)} /> </div> <div className="game-info"> <div>{status()}</div> <ol>{moves}</ol> </div> </div> ); }; export default Game;■ Jest, Enzyme でテストを実装
準備
必要なライブラリ
- jest
- enzyme
- enzyme-adapter-react-16
- react-test-renderer
最新のVerを下記コマンドで調べて、yarn addで追加する
e.g.
$ npm info jest $ yarn add --dev jest@25.4.0最終的な package.json
"devDependencies": { "enzyme": "3.11.0", "enzyme-adapter-react-16": "1.15.2", "jest": "25.4.0", "react-test-renderer": "16.13.1" }テストコードを書くまで
/src直下に以下のようなファイルを作る。
create react app で作成したプロジェクトの場合、ここに置けば自動で読み込んでくれる。
それ以外の場合は、package.json?あたりにsetupFileのパスを設定してあげれば良いと思う。ref.
- https://github.com/enzymejs/enzyme/issues/1265#issuecomment-336740161
- https://github.com/enzymejs/enzyme/issues/1265#issuecomment-336872722/src/setupTests.js
import { configure } from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; configure({ adapter: new Adapter() });テストコードを書く場所
テストコードは、テストしたいコンポーネントがあるディレクトリに
__tests__ディレクトリを切り、その中に作成する。
具体的には下記のような感じに配置する。├── components │ ├── Board.jsx │ ├── Game.jsx │ ├── Square.jsx │ └── __tests__ │ ├── Board.test.jsx │ ├── Game.test.jsx │ └── Square.test.jsx ├── index.css ├── index.js └── setupTests.js実際のテストコード
Squareコンポーネントを例に取る。
Ruby の Rspec を書いたことがある人であれば、すんなり理解できると思う。Square
import React from 'react'; // shallow関数は、引数に渡されたReactコンポーネントのみをテストするために使う。 import { shallow } from 'enzyme'; import Square from '../Square'; describe('<Square />', () => { const propsValue = 'sample text'; // mock関数 const onClickFunction = jest.fn(); const props = { value: propsValue, onClick: onClickFunction, }; // こうすることでテストのための便利な関数等が使えるようになる const wrapper = shallow(<Square {...props} />); it('text is equal props value.', () => { expect(wrapper.text()).toEqual(propsValue); }); it('when click, onClick function is called.', () => { // コンポーネントをクリックする wrapper.simulate('click'); // onClickFunctionが呼ばれることを確認する expect(onClickFunction).toBeCalled(); }); });
console.log(wrapper.debug())で中身が見れるので、うまくテストが通らないとき等に使うと良い
- styled-component を使用すると、指定した名前と変わってしまっていることがある。
最後に
- 冒頭でもリンクを載せたが、そんなこんなで最終的に出来上がったコードがこちら
- チュートリアル終わった後でも色々とやれることはあるなー。
参考
- 投稿日:2020-04-30T22:21:51+09:00
setTimeoutとsetInterval
JavaScriptの関数
繰り返し手順で必要な4つの関数
setTimeout,clearTimeout,setInterval,clearIntervalという4つのグローバル関数について
setTimeoutとsetIntervalは、指定した時間(ミリ秒)が経過したらコールバック関数を呼び出す関数。
コールバック関数とは、他の関数の引数として指定された関数のこと。(関数Aに引数として渡す別の関数B)setTimeout・・・一度だけコールバック関数を呼び出す。
setInterval・・・ブラウザを閉じるか、ページを移動するまで繰り返す。呼び出しを止めるには、
setTimeoutかsetIntevalの戻り値をclearTimeoutとclearIntervalへ渡す。let タイムアウトID = setTimeout(コールバック関数,ミリ秒); let インターバルID = setInterval(コールバック関数,ミリ秒); clearTimeout(タイムアウトID); clearInterval(タイムアウトID);以上
現在JavaScriptの学習中でタイマーを作っていたところ、重要だど感じたので書きました。
- 投稿日:2020-04-30T22:16:54+09:00
もっと気持ち悪く if を関数化したい
仕組みについては Haskell の記事を読んでいただきたく。
// 代数的データ型を関数に、 // パターンマッチを関数適用に見立てる宣言 const caseOf = match => adt => adt(match); // Haskell の Maybe を意識した何か const Nothing = () => ({Nothing}) => Nothing(); const Just = x => ({Just}) => Just(x); const guard = flg => flg ? Just() : Nothing(); const fmap = f => caseOf({ Nothing, Just: x => Just(f(x)) }); const fromMaybe = v => caseOf({ Nothing: () => v, Just: x => x }) // 定数関数 const con$t = x => _ => x; // ifの代替関数の作成用 const if_ = guard const then_ = x => fmap(con$t(x)) const else_ = fromMaybe const compose = fs => fs.reduce((g, f) => x => f(g(x)), x => x); // 使用例 const dayText = compose([ if_, then_(() => "お休み"), else_(() => "平日"), lazy => lazy() ]); console.log(dayText(true)); //=> お休み console.log(dayText(false)); //=> 平日
- 投稿日:2020-04-30T21:43:18+09:00
Kinx ライブラリ - Net.Http
Network - Http
はじめに
「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。言語はライブラリが命。ということでライブラリの使い方編。
今回は Network(Http) です。
- 参考
- 最初の動機 ... スクリプト言語 KINX(ご紹介)
- 個別記事へのリンクは全てここに集約してあります。
- リポジトリ ... https://github.com/Kray-G/kinx
- Pull Request 等お待ちしております。
libcurl を抱えているので Http 以外も作れるはずだが、まずは一番欲しいのは Http/Https でしょう。
HTTP/HTTPS リクエスト
各メソッドの意味は後で記載することとして、まずは使い方から。
Http クライアント
Http クライアント・オブジェクトは
Net.Httpクラスを使用する。var http = new Net.Http();HTTP メソッド
head、get、post、put、deleteをサポート。HTTP HEAD -
head
HEADメソッドで情報を取得する。SSL に関しては一先ず false にして実行。var http = new Net.Http(); http.sslVerifyPeer(false); http.sslVerifyHost(false); var r = http.head("https://docs.ruby-lang.org/ja/latest/class/Range.html"); if (r.code == 200) { System.println(r.toJsonString(true)); }コールバック関数を指定することもできる。データは取得できたデータ分だけ毎回コールバックされる。その動作はメソッドに関わらず全て同じ動作をする。
また、データは単なるバイト列として扱われるため、日本語などのマルチバイト文字が混じる場合には区切り位置が文字コードの区切りとぴったりあった場所で区切られるとは限らないことに注意。なので、以下のように直接出力した場合、日本語が混じると変になる可能性がある。ヘッダはまだマシだがボディの場合は基本的にはバッファにためていくのが良いかと。
var http = new Net.Http(); http.sslVerifyPeer(false); http.sslVerifyHost(false); var r = http.head("https://docs.ruby-lang.org/ja/latest/class/Range.html", &(data) => { System.print(data); }); if (r.code == 200) { ... }コールバックでの取得結果は以下の通り。
HTTP/1.1 200 OK Connection: keep-alive Content-Length: 48121 Server: nginx/1.14.2 Content-Type: text/html Last-Modified: Wed, 15 Apr 2020 00:16:18 GMT ETag: "5e965252-bbf9" Cache-Control: public, max-age=43200, s-maxage=172800, stale-while-revalidate=86400, stale-if-error=604800 Accept-Ranges: bytes Date: Mon, 27 Apr 2020 07:25:51 GMT Via: 1.1 varnish Age: 12363 X-Served-By: cache-tyo19947-TYO X-Cache: HIT X-Cache-Hits: 1 X-Timer: S1587972352.977171,VS0,VE1 Vary: Accept-Encoding復帰値
rにはヘッダ情報をオブジェクト化したオブジェクトが返され、r.codeに HTTP ステータスコードが格納されており、r.bodyにボディの情報がテキストとして格納されている。この構造は他のメソッドでも同様。HEAD ではボディは無いためr.body == ""となる。上記の例では以下のようになる。
{ "Last-Modified": "Wed, 15 Apr 2020 00", "X-Cache-Hits": "1, 1", "body": "", "Server": "nginx/1.14.2", "code": 200, "Date": "Mon, 27 Apr 2020 07", "Via": "1.1 varnish", "X-Cache": "HIT, HIT", "Content-Type": "text/html", "Cache-Control": "public, max-age=43200, s-maxage=172800, stale-while-revalidate=86400, stale-if-error=604800", "X-Timer": "S1587972352.977171,VS0,VE1", "ETag": "\"5e965252-bbf9\"", "Vary": "Accept-Encoding", "Content-Length": 48121, "Accept-Ranges": "bytes", "X-Served-By": "cache-tyo19947-TYO", "Connection": "keep-alive", "Age": 12363 }
head以外のメソッドではbodyにボディ・データがテキストの形で格納される。なので、JSON 形式で受け取る場合などはJSON.parse(r.body)と自分でパースする必要があるので注意。HTTP GET -
get
GETメソッドで情報を取得する。コールバックを指定しなければ全てを受信し、情報を格納したオブジェクトを返す。r.bodyは文字列なので、JSON 形式等で受信した場合は自分でJSON.parse(r.body)等を行う。var http = new Net.Http(); http.sslVerifyPeer(false); http.sslVerifyHost(false); var r = http.get("https://docs.ruby-lang.org/ja/latest/class/Range.html"); if (r.code == 200) { System.println(r.toJsonString(true)); }応答はこんな感じ。
{ "Last-Modified": "Wed, 15 Apr 2020 00", "X-Cache-Hits": 1, "body": "<!DOCTYPE html>\n<html lang=\"ja-JP\">\n<head>\n ...省略(長い)...", "Server": "nginx/1.14.2", "code": 200, "Date": "Thu, 30 Apr 2020 12", "Via": "1.1 varnish", "X-Cache": "HIT", "Content-Type": "text/html", "Cache-Control": "public, max-age=43200, s-maxage=172800, stale-while-revalidate=86400, stale-if-error=604800", "X-Timer": "S1588250498.519558,VS0,VE1", "ETag": "\"5e965252-bbf9\"", "Vary": "Accept-Encoding", "Content-Length": 48121, "Accept-Ranges": "bytes", "X-Served-By": "cache-tyo19929-TYO", "Connection": "keep-alive", "Age": 102 }コールバックはヘッダーとボディそれぞれ設定できる。
http.get("https://docs.ruby-lang.org/ja/latest/class/Range.html", { header: &(data) => { ... }, body: &(data) => { ... }, });コールバックに直接関数オブジェクトを指定した場合、ボディのコールバックとして動作する。
http.get("https://docs.ruby-lang.org/ja/latest/class/Range.html", &(data) => { ... // ボディ・データが細切れに来る。 });HTTP POST -
post
POSTメソッドを発行する。POST するデータはHttp#setPostDataメソッドで設定する。その他、コールバック等はGETと同じ。
POSTはなかなか試す場所が無いのだが、Ruby で以下のサーバを立ち上げてアクセスしてみる。エラーはするが、アクセスされていることは確認できる。ruby -rwebrick -e 'WEBrick::HTTPServer.new(:DocumentRoot => "./", :Port => 8000).start'アクセスしてみる。
var http = new Net.Http(); http.setPostData("POST-DATA"); http.post("http://localhost:8000/index.html", System.print);GET と同じようにボディがコールバックされてくるため、以下の応答となる。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN"> <HTML> <HEAD><TITLE>Not Found</TITLE></HEAD> <BODY> <H1>Not Found</H1> `/index.html' not found. <HR> <ADDRESS> WEBrick/1.4.2 (Ruby/2.5.1/2018-03-29) at localhost:8000 </ADDRESS> </BODY> </HTML>サーバーのログは以下の通り。
[2020-04-21 17:33:53] ERROR `/index.html' not found. ::1 - - [21/Apr/2020:17:33:53 DST] "POST /index.html HTTP/1.1" 404 280 - -> /index.htmlHTTP PUT -
put
PUTはPOSTと同じで、メソッド名だけPUTに変更して実行する。var http = new Net.Http(); http.setPostData("PUT-DATA"); http.put("http://localhost:8000/index.html", System.print);HTTP DELETE -
delete
DELETEもGETと同じで、メソッド名だけDELETEに変更して実行する。var http = new Net.Http(); http.delete("http://localhost:8000/index.html", System.print);verbose
Http#setDebug(callback)を使ってデバッグ情報を取得することができる。以下はデバッグ情報だけ出力させる例。var http = new Net.Http(); http.setDebug(System.print); http.delete("http://localhost:8000/index.html");Curl の Verbose 情報を見ることができる。
* Trying ::1:8000... * Connected to localhost (::1) port 8000 (#0) > DELETE /index.html HTTP/1.1 Host: localhost:8000 Accept: */* * Mark bundle as not supporting multiuse < HTTP/1.1 405 Method Not Allowed < Content-Type: text/html; charset=ISO-8859-1 < Server: WEBrick/1.4.2 (Ruby/2.5.1/2018-03-29) < Date: Tue, 21 Apr 2020 08:49:07 GMT < Content-Length: 302 < Connection: close < * Closing connection 0ちゃんと
DELETEメソッドが発行されてますね。さらに、
setDebugDetail()メソッドで true を指定すると、送受信したデータを表示することも可能。データ量が多くなるので、適宜コントロールすること。var http = new Net.Http(); http.setDebugDetail(true); http.setDebug(System.print); http.delete("http://localhost:8000/index.html");こんな感じになる。
* Trying ::1:8000... * Connected to localhost (::1) port 8000 (#0) > DELETE /index.html HTTP/1.1 Host: localhost:8000 Accept: */* * Mark bundle as not supporting multiuse < HTTP/1.1 405 Method Not Allowed < Content-Type: text/html; charset=ISO-8859-1 < Server: WEBrick/1.4.2 (Ruby/2.5.1/2018-03-29) < Date: Tue, 21 Apr 2020 09:09:27 GMT < Content-Length: 302 < Connection: close < > Recv data, 0000000302 bytes (0x0000012e) 0000: 3c 21 44 4f 43 54 59 50 45 20 48 54 4d 4c 20 50 <!DOCTYPE HTML P 0010: 55 42 4c 49 43 20 22 2d 2f 2f 57 33 43 2f 2f 44 UBLIC "-//W3C//D 0020: 54 44 20 48 54 4d 4c 20 34 2e 30 2f 2f 45 4e 22 TD HTML 4.0//EN" 0030: 3e 0a 3c 48 54 4d 4c 3e 0a 20 20 3c 48 45 41 44 >.<HTML>. <HEAD 0040: 3e 3c 54 49 54 4c 45 3e 4d 65 74 68 6f 64 20 4e ><TITLE>Method N 0050: 6f 74 20 41 6c 6c 6f 77 65 64 3c 2f 54 49 54 4c ot Allowed</TITL 0060: 45 3e 3c 2f 48 45 41 44 3e 0a 20 20 3c 42 4f 44 E></HEAD>. <BOD 0070: 59 3e 0a 20 20 20 20 3c 48 31 3e 4d 65 74 68 6f Y>. <H1>Metho 0080: 64 20 4e 6f 74 20 41 6c 6c 6f 77 65 64 3c 2f 48 d Not Allowed</H 0090: 31 3e 0a 20 20 20 20 75 6e 73 75 70 70 6f 72 74 1>. unsupport 00a0: 65 64 20 6d 65 74 68 6f 64 20 60 44 45 4c 45 54 ed method `DELET 00b0: 45 27 2e 0a 20 20 20 20 3c 48 52 3e 0a 20 20 20 E'.. <HR>. 00c0: 20 3c 41 44 44 52 45 53 53 3e 0a 20 20 20 20 20 <ADDRESS>. 00d0: 57 45 42 72 69 63 6b 2f 31 2e 34 2e 32 20 28 52 WEBrick/1.4.2 (R 00e0: 75 62 79 2f 32 2e 35 2e 31 2f 32 30 31 38 2d 30 uby/2.5.1/2018-0 00f0: 33 2d 32 39 29 20 61 74 0a 20 20 20 20 20 6c 6f 3-29) at. lo 0100: 63 61 6c 68 6f 73 74 3a 38 30 30 30 0a 20 20 20 calhost:8000. 0110: 20 3c 2f 41 44 44 52 45 53 53 3e 0a 20 20 3c 2f </ADDRESS>. </ 0120: 42 4f 44 59 3e 0a 3c 2f 48 54 4d 4c 3e 0a BODY>.</HTML>. * Closing connection 0
setDebugDetail()のオプションに{ hex: false }を指定すると、16進データは表示しない形式で取得することもできる。var http = new Net.Http(); http.setDebugDetail(true, { hex: false }); http.setDebug(System.print); http.delete("http://localhost:8000/index.html");16進ダンプが無くなる。
* Trying ::1:8000... * Connected to localhost (::1) port 8000 (#0) > DELETE /index.html HTTP/1.1 Host: localhost:8000 Accept: */* * Mark bundle as not supporting multiuse < HTTP/1.1 405 Method Not Allowed < Content-Type: text/html; charset=ISO-8859-1 < Server: WEBrick/1.4.2 (Ruby/2.5.1/2018-03-29) < Date: Tue, 21 Apr 2020 09:09:48 GMT < Content-Length: 302 < Connection: close < > Recv data, 0000000302 bytes (0x0000012e) 0000: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">.<HTML>. <HEAD 0040: ><TITLE>Method Not Allowed</TITLE></HEAD>. <BODY>. <H1>Metho 0080: d Not Allowed</H1>. unsupported method `DELETE'.. <HR>. 00c0: <ADDRESS>. WEBrick/1.4.2 (Ruby/2.5.1/2018-03-29) at. lo 0100: calhost:8000. </ADDRESS>. </BODY>.</HTML>. * Closing connection 0Fiber
Fiber を使ってデータをループで処理させることもできる。EventMachine 的な使い方を想定しているが、まだ EventMachine は無いので今後の検討項目。
var http = new Net.Http(); http.sslVerifyPeer(false); http.sslVerifyHost(false); var fiber = new Fiber(&{ http.get("https://docs.ruby-lang.org/ja/latest/class/Range.html", &(data) => { yield data; }); }); var total = ""; while (true) { var d = fiber.resume(); break if (!fiber.isAlive()); total += d; System.println("read %d bytes, total %d bytes" % d.length() % total.length()); } System.println(total);こんな感じで取得できます。
read 1371 bytes, total 1371 bytes read 1371 bytes, total 2742 bytes read 1371 bytes, total 4113 bytes read 1371 bytes, total 5484 bytes read 1371 bytes, total 6855 bytes read 1371 bytes, total 8226 bytes read 1371 bytes, total 9597 bytes read 1371 bytes, total 10968 bytes read 1371 bytes, total 12339 bytes read 1371 bytes, total 13710 bytes read 1371 bytes, total 15081 bytes read 1371 bytes, total 16452 bytes read 1371 bytes, total 17823 bytes read 1371 bytes, total 19194 bytes read 1371 bytes, total 20565 bytes read 1371 bytes, total 21936 bytes read 1371 bytes, total 23307 bytes read 1371 bytes, total 24678 bytes read 1371 bytes, total 26049 bytes read 1371 bytes, total 27420 bytes read 1371 bytes, total 28791 bytes read 1371 bytes, total 30162 bytes read 1371 bytes, total 31533 bytes read 1371 bytes, total 32904 bytes read 1371 bytes, total 34275 bytes read 1371 bytes, total 35646 bytes read 1371 bytes, total 37017 bytes read 1371 bytes, total 38388 bytes read 1371 bytes, total 39759 bytes read 1371 bytes, total 41130 bytes read 1371 bytes, total 42501 bytes read 1371 bytes, total 43872 bytes read 1371 bytes, total 45243 bytes read 1371 bytes, total 46614 bytes read 1371 bytes, total 47985 bytes read 136 bytes, total 48121 bytes <!DOCTYPE html> <html lang="ja-JP"> <head> <!-- Global Site Tag (gtag.js) - Google Analytics --> <script async src="https://www.googletagmanager.com/gtag/js?id=UA-620926-3"></script> <script> window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments)}; gtag('js', new Date()); gtag('config', 'UA-620926-3'); </script> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="../style.css"> <link rel="stylesheet" href="../syntax-highlight.css"> <link rel="icon" type="image/png" href="../rurema.png"> <link rel="canonical" href="https://docs.ruby-lang.org/ja/latest/class/Range.html"> <title>class Range (Ruby 2.7.0 リファレンスマニュアル)</title> ...(以下省略)インスタンス・メソッド
Net.Httpクラス・インスタンスに定義されているメソッドは以下の通り。正直、証明書関係はちゃんとテストできていないが、libcurl のマニュアル通りの実装をしてみた。基本的には対応する CURL オプションを設定しているだけです。何かあれば教えていただけると助かります。
メソッド 意味 sslVerifyPeer(tf) true, 証明書を sslSetCaInfo()メソッド、またはsslSetCaPath()メソッドで証明ディレクトリを指定する。 false, 左記の検証を行わない。sslVerifyHost(verify) true, SSL ピア証明書に一般名が存在するかどうかを調べ、その名前がホスト名と一致することを検証する。false, 左記の検証を行わない。 sslSetCaInfo(path) 接続先を検証するための証明書を保持するファイル名。 sslVerifyPeer()とともに使用する。sslSetCaPath(path) 複数の証明書ファイルを保持するディレクトリ。 sslVerifyPeer()とともに使用する。setDebugDetail(tf, opts) true, デバッグ情報に送受信データを含める。 optsとして{ hex: false }を指定すると送受信データに 16 進ダンプを含めない。setUserPassword(user, pass) 接続に使用するユーザー名とパスワード。 setProxy(url) リクエストを経由させる HTTP プロキシの URL。 setProxyUserPassword(user, pass) プロキシに接続するためのユーザー名とパスワード。 setTimeout(millisec) タイムアウトをミリ秒で指定する。 setDebug(callback) デバッグ情報を受け取るコールバックを指定する。コールバックを指定するとデバッグ情報を取得するよう設定される。 addHeader(key, value) 設定する HTTP ヘッダフィールドを keyとvalueで指定する。removeHeader(key) keyで示されるヘッダを削除する。setRedirect(tf, opts) サーバーが HTTP ヘッダの一部として送ってくる "Location: " ヘッダの内容をたどる。 optsとして{ max: n }を指定するとリダイレクトする回数の上限を設定できる。setPostData(data) POSTまたはPUTで送信するデータを設定する。head(url, callback) HEADメソッドを発行する。コールバックを指定しなかった場合、取得したヘッダデータのオブジェクトを返す。get(url, callbacks) GETメソッドを発行する。コールバックはヘッダ・ボディ用関数、または{ header: f1, body: f2 }でそれぞれ指定可能。コールバックを指定しなかった場合、取得したデータのオブジェクトを返す。post(url, callbacks) POSTメソッドを発行する。コールバックはヘッダ・ボディ用関数、または{ header: f1, body: f2 }でそれぞれ指定可能。コールバックを指定しなかった場合、取得したデータのオブジェクトを返す。put(url, callbacks) PUTメソッドを発行する。コールバックはヘッダ・ボディ用関数、または{ header: f1, body: f2 }でそれぞれ指定可能。コールバックを指定しなかった場合、取得したデータのオブジェクトを返す。delete(url, callbacks) DELETEメソッドを発行する。コールバックはヘッダ・ボディ用関数、または{ header: f1, body: f2 }でそれぞれ指定可能。コールバックを指定しなかった場合、取得したデータのオブジェクトを返す。おわりに
ネットワーク系は色々使い道があるので今後充実させたいカテゴリー。まずは一番使いたい HTTP を用意してみました。libcurl ではできないものも含めてサポートしていきたい。SNMP とか、SSH とか。
ではまた次回。
- 投稿日:2020-04-30T21:15:39+09:00
【サクラエディタ】文字列を一括で複数置換する
plus1.js//☆使い方☆ //①エクセルの1行目に置換前、2行目に置換後を記入。 //②対象データ範囲をコピーし、クリップボードに登録。 //③処理対象文字列を選択し、マクロ実行。 var listConv = function (inStr, inClip){ var convList = inClip.split('\r\n'); for(var i in convList){ var convRecord = convList[i].split('\t'); var oldStr = convRecord[0]; var newStr = convRecord[1]; if(typeof oldStr === 'string' && typeof newStr === 'string'){ inStr = inStr.replace(new RegExp(oldStr,'g'), newStr); } } return inStr; } //(INPUT)選択範囲を取得 var str = Editor.GetSelectedString(0); //(INPUT)クリップボードを取得 var clip = Editor.GetClipboard(0); //(PROCESS)クリップボードの文字列を利用して、順次置換 str = listConv(str, clip); //(OUT)編集したテキストを設定 Editor.InsText(str);使用例
サクラエディタの処理対象行(2~5行)を選択して、当マクロを実行する。
実行前
TEST1 TEST1TEST2TEST3 TEST44TEST55TEST66 TEST777TEST888TEST999 TEST10 TEST1実行後
TEST1 TEST①TEST②TEST③ TEST④④TEST⑤⑤TEST⑥⑥ TEST⑦⑦⑦TEST⑧⑧⑧TEST⑨⑨⑨ TEST⑩ TEST1
- 投稿日:2020-04-30T20:03:28+09:00
【初心者】addEventListenerでnullエラーが出る。
インターネットを参考にしながらjavaScriptでトグルメニューを作る方法を勉強した。
しかし、Chromeにて、いざ実行すると
「Cannot read property 'addEventListener' of null」
というエラーが発生する。
はい?何故ですか??
エラー文を読むと、「addEventListener」が「null」である事が分かる。
id関係のエラーであると予測し、文法ミスをくまなくチェック。
しかし、思い当たる節がない。
あれこれ文字を変えてみるも、結果は変わらず。
何故????休憩を挟み、googleで検索しまくった結果、ついに解決した。
原因はHTMLにあった。
HTMLにJavascriptを関連付けする為のscriptタグを上部に書いていたのがダメだったようだ。
そこに記述してしまうと、HTMLを読み込む前にjavascriptが動作してしまうようだ。
私は、てっきり、「.css」と同じようにhead部に記述すれば良いものだと思っていた・・・。この後、headerタグの下部に配置させる事で、無事に問題は解決した。
思い返してみれば、些細な原因だが、解決するまでに丸一日費やしてしまった・・・。
- 投稿日:2020-04-30T19:47:51+09:00
初心者がWordPressでタイピングのリアルタイム対戦ゲームを作ってみました
はじめに
この記事は、WordPressでしかホームページを作れないプログラミング初心者が、無理やりWordPressを使って、タイピングのリアルタイム対戦ゲームを作ってみた記事です。
タイピング初心者用に、70近くあるステージをクリアしていくモードもあります。IT系の集まりで、「WordPressでこのサイト作ったよ」って言ったらどよめきが起こり、「Qiitaに書いてみれば?」と言われたので書いてみます。
ちなみに、タイピンガーZというサイトです。
このサイトをWordPressでどうやって作ったのかを、ざっくり書いていこうと思います。
テーマのテンプレート
はるか昔に購入した有料テンプレートを使ってみましたが、改造しすぎて原型をとどめていないので、なんでも良かったと思います。
記事投稿と固定ページテンプレート
タイピンガーZは、対人対戦を除いて、ステージをクリアしていく仕組みになっているのですが、それらのステージはWordPressの記事投稿で作りました。
記事には、敵キャラのセリフだったり、敵の強さだったり、音楽・背景だったりのパラメーターを記入します。
↓こんな感じ<!--セリフ--> <span id="pan2serifu"> (たけしさんブリーフ増えてる・・・)brbr なんすかそれ, ブリーフ型ターバンヲ発明シマシタ。22, ヨガタケシデース22, カレーガンジーヨガファイア22, インド人に謝れ, ヨガソーリー22, さて、今回もがんばろう。 ,*-,*del,((</span> <!--速度基準--> <span id="speedlinesetting1">20</span><!--遅い--> <span id="speedlinesetting2">30</span> <span id="speedlinesetting3">40</span><!--早い--> <!--ミス基準--> <span id="misslinesettei1">60</span><!--正確性低い--> <span id="misslinesettei2">70</span> <span id="misslinesettei3">85</span><!--正確性高い-->それぞれのパラメーターは、Javascriptでゲットできるように、idかclassを付けておきます。
セリフに「22」とか、「*del」とか書いてますが、これはJavascript側に何かしてほしいときの自作コマンドみたいなものです。
このコマンドで、セリフを話してるキャラが消えたり、セリフが大きくなったり、BGMが変わったりと、いろいろ操作することができるようにしてます。また、これだけではただのブログ記事のデザインにしかならないので、自分で作った固定ページテンプレートを当てます。
「初心者練習ステージテンプレート」「CPU対戦用テンプレート」などをPHPで書いて用意しました。
あとはパラメーターを書いた記事を大量に投稿して、それぞれに固定ページテンプレートを当てれば、ステージがどんどん増えていきます。
リアルタイム対戦
リアルタイム対戦を導入するにあたってNode.jsを使いたかったのですが、僕が使っているロリポップレンタルサーバーが対応してなかったので、仕方なくAWSも使うことにしました。
AWSを「オッス」と読んでたくらい何も知識がなかったのですが、1年目が無料なのと、2年目以降もだいぶ格安っぽい感じだったので使うことにしました。
AWSでNode.jsを導入するにあたって、下記の記事をそのままやりました。
この記事でやってることが何なのか、何も理解しないまま人形のように作業したのですが、なんと普通にNode.jsが動きました。
記事の筆者さんありがとう。しかし、この記事のままだとhttpsでのやり取りができず、そこではまりました。
何も理解しないまま進めると、応用が利かないから困ります。なんとか解決したものの、どうやって解決したのか忘れてしまったという。。。
たしかロードバランサ―の部分でhttpsを受け入れてなかったとか、そんな感じだった気がします。Socket.io
Node.jsのライブラリであるSocket.ioを使って、リアルタイム対戦を実現しました。
Socket.ioまじですごい。アホでも使えるようにしてくれてる。基本的には、下の四つだけ覚えたら使えました。
.on() 受信
.emit() 送信
.broadcast() 自分以外に送信
.join() 部屋作るFirebaseとやらがもっと簡単という噂を聞きましたが、Socket.ioも初心者でも扱えるとてもシンプルなものでした。
処理のフローをまとめると
無理やりWordpress使って、サーバーも2つ使ってるという構造なので、無駄に複雑です。
リアルタイム対戦の処理をまとめると・・・
1、サーバー1(WordPress)からクライアントにページ情報を送信
2、クライアント側でHTMLとかJavascriptが動く
3、クライアントからサーバー2(AWS、Node.js)に情報送信
4、サーバー2から対戦相手のクライアントに情報送信
5、以下サーバー2を通してクライアント同士で情報送受信という流れです。
Qiitaの天才達、ありがとう
開発前は、プログラミング素人の自分が、リアルタイム対戦なんてできるのか?
とか思ってましたが、やってみればできるもんです。
10年前だったら不可能だったと思いますが、先人の天才達が素人でもできるように、資料やライブラリを公開してくれているので、なんとかなった感があります。
Qiita、めちゃくちゃ参考にしました。天才達、ありがとう。
Twitter:@pant2taicho
ホームページ:タイピンガーZ
- 投稿日:2020-04-30T19:32:27+09:00
Angularでアプリのテーマ管理
はじめに
世の中にダークモードが流行ってる中、ダークモードを実装するには色んな方法がありますが、今回はAngularでアプリのテーマ (背景、色)を管理してみった。
今回のアプリに使用したものはこんな感じです。
- Angular 9
- Angular Material 9.2.1
Angular Materialによるカラー管理
Angular Materialのインストールと設定が終わりましたら、まずはテーマ作成用のscssファイルを作成する。
最初はAngular Materialのテーマ機能をインポートして、コアmaxinをincludeする。
これでAngular部品のテーマ作成機能が使えるようになりました。カラーパレットを設定
Angular Materialのカラーパレットはprimary(基本)、accent(アクセント)、warn(警告)の三色で構成されています。SCSS変数でそれぞれを設定して、mat-light-theme maxinに注ぎます。Materialパレットはこちらで参考してください(https://material.io/resources/color/)
他の部品を見てみると....
必須項目の色がちゃんとwarnの色になっていますね!変更用テーマを作成
ここまでデフォルトのテーマを設定できましたので、ここからは変更用のテーマをもう一個作成します。
今までのやり方と変わらず、ダークテーマ用のSCSS変数を設定して、mat-dark-themeに注ぎます。
あとはこのように好きなクラスで囲き込みます。これによって、"unicorn-dark-theme"のDOM要素はこのテーマに適用するようになります。
テーマ変更ボタン(一番右のボタン)をメニューに追加する
ボタンを押すとテーマサービスで対象テーマを適用する
テーマサービスが処理行ったらイベントをAppコンポーネントに送って、対応するクラスをDOM要素に追加する。これで完成です。
最終的にこうなります
まとめ
Angular MaterialのTheming機能を紹介させていただきました。
Stackblitzにセットアップなしで試すことできますので、ぜひ皆様もやってみましょう!
今回の成品をstackblitzに載っていますので、良かったら参考してみてください!
(https://angular-crosscomponent.stackblitz.io/)
- 投稿日:2020-04-30T19:29:17+09:00
【忘備録】Javascript : varとlet、宣言に使うべきは?スコープの違いから理解
はじめに
今まで Javascript で変数宣言をする際に let を使っていたけど、その理由がよくわかっていなかったのでまとめます。
まとめ
・ 変数には「グローバル変数」と「ローカル変数」があるよ。
・ 基本型と参照型の変数で処理の流れが変わるよ。
・ やっぱり、varよりletを使った方がいいよ。先ず、最初にスコープのおさらい。
⑴グローバル変数とローカル変数
基本、関数内で定義された変数が「ローカル変数」です。
ローカル変数は関数全体でのみ有効な為、グローバル変数に影響しません。javascript.jsvar scope = "global"; function getScope(){ var scope = "local"; return scope; } console.log(getScope()); //=> local console.log(scope); // => global上のコードを見てもらうとわかる通り、グローバル変数 scope とローカル変数 scope は同じ変数名でも互いに影響し合わない。
ローカル変数は関数全体でのみ有効なので。
※ちなみに上のコードから var 宣言を取り除いてみます。javascript.jsscope = "global"; function getScope(){ scope = "local"; return scope; } console.log(getScope()); // getScope によって scope に local が格納される。=> local console.log(scope); // => localvar を外して変数を宣言すると全ての変数がグローバル変数扱いになってしまうので2行目の
console.log(scope)でもlocalが出力されてしまう。
つまりローカル変数がこの場合、存在しないので1行目のconsole.log(getScope())で関数を呼び出した時点でscopeがlocalに上書きされてしまっている。⑵基本型と参照型による変数の処理方法の違い
先ず、前提として基本型は値自体を変数に格納している。
そして参照型は変数の値が格納されているメモリのアドレスを格納している。(格納している場所情報が変数に格納されているイメージ)。1,基本型
javascript.jsvar i = 10; function decrement(val){ val--; return val; } console.log(decrement(i)); // => 9 console.log(i); // => 102,参照型
javascript.jsvar arry = [1,2,3,4,5,6]; function deleteElement(val){ val.pop();// 末尾の要素を削除 return val; } console.log(arry); // => [1,2,3,4,5,6] console.log(deleteElement(arry)); // => [1,2,3,4,5] console.log(arry);// => [1,2,3,4,5]基本型と参照型の最後に出力されている値を見比べていただくとわかるが、
基本型の場合、引数に入れられたグローバル変数が関数内で処理されるも、その後にグローバル変数を出力すると宣言時の値が出力される。
一方、参照型の場合、引数に入れられたグローバル変数が関数内で処理され、その後にグローバル変数を出力しようとすると関数内で処理された結果が再び出力される。
前提でも話した通り、参照型は変数に値を入れている訳でなく、値のメモリ上のアドレスを格納しているため、一度その場所に格納してある値をいじったら、次にアドレスを参照しても、いじられた後の値が見つかる。
※一方、基本型の場合は最初に宣言されているグローバル変数が関数内に入り処理されるため、処理された変数は関数内でのみ有効になり、グローバル変数に影響は与えないと理解しました。varとletの違い
通常、Javaなどではブロック内で定義された変数はブロックの外では使えない為、あらかじめ、ブロックの外で宣言しておく。それによって一度、ブロック内に入った変数もブロックを抜けても外で使えていた。
その方式に乗っ取りかくと以下のようになります。javascript.jsvar i = 4 if (true) { i++; } console.log(i); // => 5
しかしjavascriptにはブロックレベルのスコープが存在しない為、以下のような書き方ができる。javascript.jsif (true) { var i = 4; i++; } console.log(i); // => 5しかし一般的にこの書き方は「スコープは出来るだけ限定すべき」という一般的ルールから逸脱したものであるため、javaに慣れてきた今となっては分かりづらいし、覚えるのが面倒。
そこで let の使用
上のコードで宣言されている
var i = 4;のvarをletに置き換えてみよう。javascript.jsf (true) { let i = 4; } console.log(i); // => Uncaught ReferenceError: i is not defined見ての通り、ブロックレベルのスコープが働き、ifブロック内で宣言した変数はブロック外ではエラーを吐いてくれるようになりました。これで「スコープは出来るだけ限定すべき」という一般的ルールから逸脱することなく、分かりやすいコードをかけます。
つまり、varはブロックレベルのスコープを認識できず、letはブロックレベルのスコープを認識できることがわかった。以上のことを踏まえてやはり変数宣言はletを使用した方が良いかも。
おまけ
switch分も一つのブロックの為、let宣言は変数の重複エラーになる。
javascript.jslet x = 2; switch (x) { case 0: let value = {x:0}; break; case 1: let value = {x:1}; break; case 2: let value = {x:2} break; } console.log(value); // => Uncaught SyntaxError: Identifier 'value' has already been declared解決策
1、最初に変数宣言を行ってしまう
javascript.jslet x = 2; let value = {x:100}; // ここで変数宣言 switch (x) { case 0: value = {x:0}; // let消す break; case 1: value = {x:1}; // let消す break; case 2: value = {x:2} // let消す break; } console.log(value); // => {x:2}2、ブロック内でvar宣言で変数宣言を行う
javascript.jslet x = 2; switch (x) { case 0: var value = {x:0}; // letをvarに変更 break; case 1: var value = {x:1}; // letをvarに変更 break; case 2: var value = {x:2}; // letをvarに変更 break; } console.log(value); // => {x:2}スコープは出来る限り限定すべき、という一般的ルールから外れてしまうので⑴の方がいいかな。
最後に
なんとなく使ってモヤモヤしていたvarとletの違いについて改めて勉強することができてよかったです。多分、もうvarは使いません。
- 投稿日:2020-04-30T18:58:04+09:00
Chart.jsとAPIによるコロナウイルスのデータのグラフ化~Vueを添えて~
はい、皆さんこんにちは!
ある日ネットサーフィンをしていたら、数値を基にグラフを作成してくれる「Chart.js」と世界のコロナウイルスに関するデータを提供してくれるAPIを見つけたので、今日はこれらとVue.jsを組み合わせて世界のコロナウイルスのデータを表示するグラフを作っていきたいと思います。今回作るもの
APIから国別のコロナウイルスについてのデータを取得し、そのデータをChar.jsを使って棒グラフとしてブラウザ(今回はGoogle Chrome)へ映すプログラム。また、今回扱うAPIはコロナウイルスについて数種類のデータを提供しているため、グラフの利用者がほしいデータの種類を利用者自身がブラウザ上でを選べるようにする。
本日の材料はこちら
- Chart.jsファイル(ダウンロードはこちら)
- API(ダウンロードはこちら)
- Vue.js(グラフの値の操作等で使うだけなので、面倒くさければjavascriptをそのまま使っても大丈夫。ちなみにダウンロードはこちら)
では、実際に作っていく!
今回の工程
1. Chart.js使い、ブラウザ上にグラフを作成する。
2. APIからデータを取得する
3. ブラウザ上からグラフを操作できる仕組みを作る1. Chart.js使い、ブラウザ上にグラフを作成する。
Chart.jsの扱い方はとても簡単です。Chart.jsを使うためには、CDNを利用するかファイル自体をダウンロードし、ブラウザへ表示したいHTMLファイルにscriptタグで埋め込むだけ!
あとはChart.jsの公式ホームページかこの記事に書いてあるコードをコピペすればすぐに使えます。
あとはコピペしたコードのChartクラス内のdataプロパティなどをいじっていれば扱い方はすぐにわかると思います。2. APIからデータを取得する
APIからデータを取り出すために、今回は
fetch()を使ってみました。コードはこんな感じjsconst infoArray = await fetch('https://api.covid19api.com/summary') //awaitはasyncがついている関数内でしか使えないので注意! .then(function(response) { return response.json(); }).then(function (data) { return data.Countries; });データは変数(今回はinfoArray)にあるので、これを少し加工してChartクラス内のdataプロパティにセットすれば、APIからの情報をグラフで表せます!
3. ブラウザ上からグラフを操作できる仕組みを作る
ブラウザだけではデータベースもなく、jsだとcookieを使ったユーザーからのデータの保持が手軽にできる方法がない。(自分が探していないだけでもしかしたらあるかも)ではどうすれば良いか?そこで今回使用するのは
sessionStorage。こちらは配列でブラウザでのユーザーの入力データを保持できます。jssessionStorage['kaneko'] = 'kinironoyatu'; sessionStorage['Orotimaru'] = 'Yu-sho-'; console.log(sessionStorage.getItem('Kaneko')); //consoleにkinironoyatuと表示される console.log(sessionStorage.getItem('Orotimaru')); //consoleにYu-sho-と表示される
localStorage.tokenでデータを保持しようと初めは考えていましたが、複数のデータを保持させることができなかったので、sessionStorageを使用しました。もしsessionStorageについてもう少し知りたい場合は下の記事を見てみてください。
Qiita sessionStorageをつかってみるこの
sessionStorageを使用して作成した見た目(HTML)とその機能(Vue.js)がこちらbody<p>Select a category you want to know.</p> <div id="vue"> <form style="display: flex;"> <div v-for="category in categories"> <input type="radio" name="category" :value=category v-model="select">{{category}} //見たいデータのカテゴリーの選択しの表示 </div> <button @click="selectCategory" style="margin:0 20px;">check!</button> //カテゴリーの切り替え <button @click="back" style="margin:0 10px;">back</button> //次の27か国のデータを表示 <button @click="next" style="margin:0 10px;">next</button> //前の27か国のデータを表示 </form> </div> <canvas id="myChart"></canvas> //グラフの表示Vue.jsconst categories = ['NewConfirmed', 'TotalConfirmed', 'NewDeaths', 'TotalDeaths', 'NewRecovered', 'TotalRecovered',] const getEntry = sessionStorage.getItem('entry') new Vue({ el:'#vue', data: { categories: categories, select: '', }, created: function () { if (!getEntry) { sessionStorage['entry'] = '0' } }, methods: { selectCategory: function () { sessionStorage['select'] = this.select }, next: function () { switch (getEntry) { case '0': sessionStorage['entry'] = '27' break case '27': sessionStorage['entry'] = '54' break case '54': sessionStorage['entry'] = '81' break case '81': sessionStorage['entry'] = '108' break case '108': sessionStorage['entry'] = '135' break case '135': sessionStorage['entry'] = '162' break case '162': sessionStorage['entry'] = '189' break default: sessionStorage['entry'] = '216' break } }, back: function () { switch (getEntry) { case `216`: sessionStorage['entry'] = '189' break case '189': sessionStorage['entry'] = '162' break case '162': sessionStorage['entry'] = '135' break case '135': sessionStorage['entry'] = '108' break case '108': sessionStorage['entry'] = '81' break case '81': sessionStorage['entry'] = '54' break case '54': sessionStorage['entry'] = '27' break default: sessionStorage['entry'] = '0' break } } } })簡単に機能を説明しますと、HTML部分の
<button>を押すとVue.jsの部分でそれに応じてsessionStorageを書き換える感じになっています。
また、もともとのAPIの総データ数があまりにも多く、ブラウザで一度にすべての国の情報を載せられなかったため表示できる国の数を制限しました。next、backを押すことでブラウザに表示されていない国のデータを表示させることができま。)あと悔しいことにsessionStorageは文字列データしか扱えないため、こんなに不格好なプログラムになってしまいました。完成したものがこちら!
ネットを漁っていたら、グラフをお手軽に作成できる作成「Chart.js」なるものとコロナの情報がお手軽に手に入るAPI(https://t.co/VqyaFTWy0n)を見つけたので、さっそく世界中のコロナの情報がグラフで見れるプログラムを書いてみました。動画は実際にこれを動かしている時のものです。 pic.twitter.com/TlVOtK9aag
— サントゥー (@SAshunchan) April 29, 2020GitHubで公開してます
git cloneなどでコードを詳しく見たい方はこちら
すぐに動かせるので、ぜひ動かしてみてください!今回の記事はこれで以上です。ではまた!
- 投稿日:2020-04-30T18:50:59+09:00
【Rails】住所を入力すると自動的に地図表示される方法(Google Maps API)
はじめに
ポートフォリオ制作で、Google Map APIを用いて住所フォームを送信した際に住所名と地図を自動的に地図表示される設定を行ったので、アウトプットも兼ねて手順を紹介していきます。
手順
1.Google MapのAPI Keyを取得
2.住所(address)、緯度(latitude)、経度(longitude)カラムを追加する
3.住所を登録するフォームを作成する
4.gem geocoderを追加する
5.viewにGoogle Mapを表示させる記述を追加する
6.詳しい住所を表示させる
7.GitHubにPushされないように設定する
8.APIキーの書き換え
(その他)AWSでデプロイしている場合1.Google MapのAPI Keyを取得
APIの取得はこちらの記事がわかりやすいので参考にしてみてください。
https://qiita.com/tiara/items/4a1c98418917a0e74cbb
2.住所、緯度、経度カラムを追加する
住所(address)、緯度(latitude)、経度(longitude)カラムを追加しましょう。
class ChangeDatatypeLanguageOfLabs < ActiveRecord::Migration[5.2] def change change_column :labs, :address, :string change_column :labs, :latitude, :float change_column :labs, :longitude, :float end endlabsはモデル名なので人によって変わります。
latitudeとlongitudeは
float型にします。floate型については下記の記事を参考にしてみてください。
https://wa3.i-3-i.info/word14968.htmlカラムを追加したら
$ rails:db:migrateしましょう。3.住所を登録するフォームを作成する
<%= form_for(@lab) do |f| %> ・・・ <%= f.label :住所 %> <%= f.text_area :address, autofocus: true %> ・・・ <%= f.submit '投稿' %> <% end %>これで住所を登録するフォームが投稿できます。
4.gem geocoderを追加する
地図表示する際に、Google Mapでは緯度・経度から位置を取得します。
なので、緯度・経度の登録を行う為に今回は
gem geocoderを導入します。
geocoderによって住所の情報を元に緯度、経度を割り出してくれます。Gemfile.gem 'geocoder'記述後、ターミナル上で
$ bundle installを実行します。lab.rbgeocoded_by :address(←住所のカラム名) after_validation :geocode, if: :address_changed?これで、モデル登録時と住所(address)変更時にgeocoderが動いて緯度・経度のデータが登録・更新されます
5.viewにGoogle Mapを表示させる記述を追加する
labs/show.html.rb・・・ <p>住所</p> <p><%= @lab.address %></p>(住所名表示) <div id="map"></div>(地図を表示) ・・・ <style>(地図の大きさ指定) #map{ height: 150px; width:270px; } </style> <script type="text/javascript"> function initMap() { var test ={lat: <%= @lab.latitude %>, lng: <%= @lab.longitude %>}; var map = new google.maps.Map(document.getElementById('map'), { zoom: 15, center: test }); var transitLayer = new google.maps.TransitLayer(); transitLayer.setMap(map); var contentString = '住所:<%= @lab.address %>'; var infowindow = new google.maps.InfoWindow({ content: contentString }); var marker = new google.maps.Marker({ position:test, map: map, title: contentString }); marker.addListener('click', function() { infowindow.open(map, marker); }); } </script> <script async defer src="https://maps.googleapis.com/maps/api/js?v=3.exp&key=取得したAPIキー&callback=initMap"> </script>これで登録した住所を元に地図表示ができるようになりました。
しかし、この状態では住所名によって地図が表示されない場合があります。それはデフォルトのgeocoderのままでは詳しい住所の経度、緯度を持ってくることができないからです。
なので、詳しい住所でも表示されるように設定していきます。
6.詳しい住所を表示させる
ターミナル上で
$ bin/rails g geocoder:configコマンドを入力。
そうすると、config/initializers/geocoder.rbファイルが作成されます。作成されたファイルを開き、中身を変更させましょう。
geocoder.rbGeocoder.configure( # Geocoding options # timeout: 3, # geocoding service timeout (secs) lookup: :google, # name of geocoding service (symbol) # ip_lookup: :ipinfo_io, # name of IP address geocoding service (symbol) # language: :en, # ISO-639 language code use_https: true, # use HTTPS for lookup requests? (if supported) # http_proxy: nil, # HTTP proxy server (user:pass@host:port) # https_proxy: nil, # HTTPS proxy server (user:pass@host:port) api_key: ENV['GOOGLE_MAP_API_KEY'], # API key for geocoding service # cache: nil, # cache object (must respond to #[], #[]=, and #del) # cache_prefix: 'geocoder:', # prefix (string) to use for all cache keys # Exceptions that should not be rescued by default # (if you want to implement custom error handling); # supports SocketError and Timeout::Error # always_raise: [], # Calculation options # units: :mi, # :km for kilometers or :mi for miles # distances: :linear # :spherical or :linear )これで、Google mapのAPIを使って緯度、経度を検索できるようになりました。
7.GitHubにPushされないように設定する
この状態でPushしてしまうと取得したAPIキーもGitHub上に上がってしまいます。
なので、Pushしても表示されないように設定しましょう。gemの
dotev-railsを用いて設定していきます。Gemfile.gem 'dotenv-rails'ターミナル上で
$ bundle installを実行します。app等と同じステージに.envファイルを作成しましょう。
.envファイルにAPIキーを記述する。env.GOOGLE_MAP_API_KEY=取得したAPIキー
.envファイルの下にある
.gitignoreファイルの最後の行に.envと記述しましょう。
(ちなみにignoreとは無視するという意味です。)gitignore.・・・ ・・・ /.envこれでPushしても.envファイルは無視されます。
8.APIキーの書き換え
今までAPIキーを直接書き込んでいた全てのファイルでAPIキーの記述を.envファイル内で定義したGOOGLE_MAP_API_KEYに書き換えます。
labs/show.html.rb<script async defer src="https://maps.googleapis.com/maps/api/js?v=3.exp&key=<%= ENV['GOOGLE_MAP_API_KEY'] %>&callback=initMap"> </script>geocoder.rbapi_key: ENV['GOOGLE_MAP_API_KEY'],これでGitHub上にAPIキーが上がることなく地図が表示されるようになります。
(その他)AWSでデプロイしている場合
AWSでデプロイしている場合、本番環境に反映させる為には
EC2にログイン後、Git pullをして変更内容を取り込みます。その後、
$ sudo vi .envコマンドで.envファイルを開き、GOOGLE_MAP_API_KEY=取得したAPIキーを記述するとデプロイ先でも反映されるようになります。
- 投稿日:2020-04-30T18:50:59+09:00
【Rails】住所を入力すると自動で地図表示される方法(Google Maps API)
はじめに
ポートフォリオ制作で、Google Maps APIを用いて住所フォームを送信した際に住所名と地図を自動で地図表示される設定を行ったので、アウトプットも兼ねて手順を紹介していきます。
手順
1.Google MapのAPI Keyを取得
2.住所(address)、緯度(latitude)、経度(longitude)カラムを追加する
3.住所を登録するフォームを作成する
4.gem geocoderを追加する
5.viewにGoogle Mapを表示させる記述を追加する
6.詳しい住所を表示させる
7.GitHubにPushされないように設定する
8.APIキーの書き換え
(その他)AWSでデプロイしている場合1.Google MapのAPI Keyを取得
APIの取得はこちらの記事がわかりやすいので参考にしてみてください。
https://qiita.com/tiara/items/4a1c98418917a0e74cbb
2.住所、緯度、経度カラムを追加する
住所(address)、緯度(latitude)、経度(longitude)カラムを追加しましょう。
class ChangeDatatypeLanguageOfLabs < ActiveRecord::Migration[5.2] def change change_column :labs, :address, :string change_column :labs, :latitude, :float change_column :labs, :longitude, :float end endlabsはモデル名なので人によって変わります。
latitudeとlongitudeは
float型にします。float型については下記の記事を参考にしてみてください。
https://wa3.i-3-i.info/word14968.htmlカラムを追加したら
$ rails:db:migrateを実行します。3.住所を登録するフォームを作成する
<%= form_for(@lab) do |f| %> ・・・ <%= f.label :住所 %> <%= f.text_area :address, autofocus: true %> ・・・ <%= f.submit '投稿' %> <% end %>これで住所を登録するフォームが投稿できます。
4.gem geocoderを追加する
地図表示する際に、Google Mapでは緯度・経度から位置を取得します。
なので、緯度・経度の登録を行う為に今回は
gem geocoderを導入します。
geocoderによって住所の情報を元に緯度、経度を割り出してくれます。Gemfile.gem 'geocoder'記述後、ターミナル上で
$ bundle installを実行します。lab.rbgeocoded_by :address(←住所のカラム名) after_validation :geocode, if: :address_changed?これで、モデル登録時と住所(address)変更時にgeocoderが動いて緯度・経度のデータが登録・更新されます
5.viewにGoogle Mapを表示させる記述を追加する
labs/show.html.rb・・・ <p>住所</p> <p><%= @lab.address %></p>(住所名表示) <div id="map"></div>(地図を表示) ・・・ <style>(地図の大きさ指定) #map{ height: 150px; width:270px; } </style> <script type="text/javascript"> function initMap() { var test ={lat: <%= @lab.latitude %>, lng: <%= @lab.longitude %>}; var map = new google.maps.Map(document.getElementById('map'), { zoom: 15, center: test }); var transitLayer = new google.maps.TransitLayer(); transitLayer.setMap(map); var contentString = '住所:<%= @lab.address %>'; var infowindow = new google.maps.InfoWindow({ content: contentString }); var marker = new google.maps.Marker({ position:test, map: map, title: contentString }); marker.addListener('click', function() { infowindow.open(map, marker); }); } </script> <script async defer src="https://maps.googleapis.com/maps/api/js?v=3.exp&key=取得したAPIキー&callback=initMap"> </script>これで登録した住所を元に地図表示ができるようになりました。
しかし、この状態では住所名によって地図が表示されない場合があります。それはデフォルトのgeocoderのままでは詳しい住所の経度、緯度を持ってくることができないからです。
なので、詳しい住所でも表示されるように設定していきます。
6.詳しい住所を表示させる
ターミナル上で
$ bin/rails g geocoder:configコマンドを入力。
そうすると、config/initializers/geocoder.rbファイルが作成されます。作成されたファイルを開き、中身を変更させましょう。
geocoder.rbGeocoder.configure( # Geocoding options # timeout: 3, # geocoding service timeout (secs) lookup: :google, # name of geocoding service (symbol) # ip_lookup: :ipinfo_io, # name of IP address geocoding service (symbol) # language: :en, # ISO-639 language code use_https: true, # use HTTPS for lookup requests? (if supported) # http_proxy: nil, # HTTP proxy server (user:pass@host:port) # https_proxy: nil, # HTTPS proxy server (user:pass@host:port) api_key: ENV['GOOGLE_MAP_API_KEY'], # API key for geocoding service # cache: nil, # cache object (must respond to #[], #[]=, and #del) # cache_prefix: 'geocoder:', # prefix (string) to use for all cache keys # Exceptions that should not be rescued by default # (if you want to implement custom error handling); # supports SocketError and Timeout::Error # always_raise: [], # Calculation options # units: :mi, # :km for kilometers or :mi for miles # distances: :linear # :spherical or :linear )これで、Google mapのAPIを使って緯度、経度を検索できるようになりました。
7.GitHubにPushされないように設定する
この状態でPushしてしまうと取得したAPIキーもGitHub上に上がってしまいます。
なので、Pushしても表示されないように設定しましょう。gemの
dotev-railsを用いて設定していきます。Gemfile.gem 'dotenv-rails'ターミナル上で
$ bundle installを実行します。app等と同じステージに.envファイルを作成しましょう。
.envファイルにAPIキーを記述する。env.GOOGLE_MAP_API_KEY=取得したAPIキー
.envファイルの下にある
.gitignoreファイルの最後の行に.envと記述しましょう。
(ちなみにignoreとは無視するという意味です。)gitignore.・・・ ・・・ /.envこれでPushしても.envファイルは無視されます。
8.APIキーの書き換え
今までAPIキーを直接書き込んでいた全てのファイルでAPIキーの記述を.envファイル内で定義したGOOGLE_MAP_API_KEYに書き換えます。
labs/show.html.rb<script async defer src="https://maps.googleapis.com/maps/api/js?v=3.exp&key=<%= ENV['GOOGLE_MAP_API_KEY'] %>&callback=initMap"> </script>geocoder.rbapi_key: ENV['GOOGLE_MAP_API_KEY'],これでGitHub上にAPIキーが上がることなく地図が表示されるようになります。
(その他)AWSでデプロイしている場合
AWSでデプロイしている場合、本番環境に反映させる為には
EC2にログイン後、Git pullをして変更内容を取り込みます。その後、
$ sudo vi .envコマンドで.envファイルを開き、GOOGLE_MAP_API_KEY=取得したAPIキーを記述するとデプロイ先でも反映されるようになります。
- 投稿日:2020-04-30T17:47:53+09:00
jsPsychによる心理学実験作成チュートリアルまとめ
はじめに
本シリーズは,サイモン課題の作成を通して,jsPsychで心理学実験を行うために最低限必要なスキルを身につけることを目標としています。
具体的には,以下の4つです。
想定しているターゲットは,どの言語のプログラミング経験もない人です。javascript(や,HTML, css)の基本を知っている必要はありません。シリーズでは, jsPsychで心理学実験を作成するのに必要なテクニックをjavascriptの基本的な構文も織り交ぜながら順番に説明していきます。もちろん,javascriptは知っているがjsPsychはこれからという人にも有用な資料になっているはずです。
なお,このチュートリアルは私が以前作成した「PsychoPy Coderによる心理学実験作成チュートリアル」をjsPsych用に書き換えたものです。
jsPsychとは
jsPsychは,javascriptというプログラミング言語で書かれた心理学実験・調査作成ツール(ライブラリ・プラグイン)です。そして,javascriptはGoogle ChromeやFirefox, Safariといったウェブブラウザ上で動作するプログラミング言語です。したがって,jsPsychもウェブブラウザ上で動作します。つまり,ウェブブラウザ上で心理学実験を動作させることができます。ウェブブラウザは,インターネットを介して世界の誰かの記事・ブログを読むためのものです。それらウェブページと同様に,jsPsychで作成した実験をウェブ上にアップロードしておけば,世界のどこかの誰かさんがインターネットを介して自分の作成した心理学実験に参加することができます。つまり,「オンライン実験」を実施することができます。もちろん,jsPsychで作成した実験をPCにダウンロードしておけば,インターネットがなくてもそのPC上で実験を動作させることができるので,jsPsychでも実験室実験を実施することができます。
オンライン実験作成がjsPsychの主な特徴になると思うのですが,他にも同様のツールは結構あるようです(Anwyl-Irvineら(2020)のTable 1)。その中でも私がjsPsychを使おうと思った理由は以下の3つです。
- 無料で使える1
- スクリプトを書いて作成する
- 日本語の資料がある
2つ目の点が個人的には結構大事でした。無料で使えて,日本語の資料があるツールとしては,PsychoPy Builderやlab.jsが挙げられます。これらのツールはjsPsychと違って,基本的にGUIでマウスでポチポチ実験を作成することができるツールです。GUIのツールは、最初の垣根は低いのですが、工夫し始めると一気に手順がややこしくなったり、コードを書いたりする必要が出てきたりするという印象があります。コードを書くとなった場合にはツールのベースにあるプログラミング言語(PsychoPyであればpython, lab.jsであればjavascript)について結局勉強する必要があります。
だったら,初めからコードでの実験の作り方を覚えたほうが長期的にはいいんじゃないかと思います。コーディングは最初の垣根がGUIより高いかもしれませんが、慣れてしまえば、GUIよりも楽に自由に実験を組めるようになります。その知識は他のプログラミング言語を勉強するときにも応用できるので、その際の学習コストも低くなります。私の場合は,PsychoPyを使ってコードを書いて心理学実験を作ってきたので,jsPsychにはすぐに慣れることができました。
とはいえ,この辺は作成する予定の実験の複雑さに左右されます。GUIツールでも全然事足りる場合はあるので,全く心理実験を作ったことがないという方は,まずはGUIツールでの作成から始めてみるのがいいと思います。
参考資料
本チュートリアルの作成に際し,以下の資料を参考にいたしました。作成者のみなさまには感謝申し上げます。
- jsPsych公式サイト
- ダウンロードしたjsPsychに同梱されているサンプルコード(examplesフォルダ内)
- 黒木先生の「ウェブブラウザで心理学実験と調査 jsPsych」
- 高橋先生の「キソジオンライン」
- 国里先生の「jsPsychを用いた認知課題の作成」
- 小林先生の「jsPsychチュートリアル」
黒木先生のサイトについては掲載のコードを見る限り,現在のバージョンと互換性がなさそうなので,注意が必要かもしれません。高橋先生のキソジオンラインは,jsPsychの解説資料ではありませんが,jsPsychで作成された心理学実験のコードがたくさんアップされています。jsPsychがある程度読み書きできるようになったら,とても参考になると思います。国里先生のページではRstudioという開発環境上でjsPsychベースの心理実験を作成する方法が紹介されています。Rstudioに馴染みのある方はここから始めてみてもいいかもしれません。小林先生のページにはこれら3つのサイトとは少し種類の違う実験課題のコードが掲載・解説されています。
おわりに
本記事は,本シリーズのまとめページとして概要を説明しました。正直に言うと,サイモン課題は,GUIツールで簡単に作成できる課題です(PsychoPy Builderでの作成,lab.jsでの作成)。
それでも,本シリーズが,なにかのきっかけにjsPsychで書きたい・書かないといけないという方のお役に立てれば大変幸いです。
ただし,オンライン実験を実施するためにはアップロードしておくサーバーが必要で,多くの場合,レンタルサーバーを借りることになるので,その実施には謝礼とは別にお金がかかります。 ↩
- 投稿日:2020-04-30T17:44:34+09:00
Reactチュートリアル ◯×ゲーム
Reactで作る ◯×ゲーム
公式サイト https://ja.reactjs.org/tutorial/tutorial.html
公式の最終コード https://codepen.io/gaearon/pen/gWWZgR?editors=0010変更するのは一つのファイル(index.js)だけ
デザインを変えたければindex.cssを変更するプロジェクトディレクトリ/src/index.jsimport React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; function Square(props) { return ( <button className="square" onClick={props.onClick} > {props.value} </button> ); } class Board extends React.Component { renderSquare(i) { return ( <Square value={this.props.squares[i]} onClick={() => this.props.onClick(i)} /> ); } render() { return ( <div> <div className="board-row"> {this.renderSquare(0)} {this.renderSquare(1)} {this.renderSquare(2)} </div> <div className="board-row"> {this.renderSquare(3)} {this.renderSquare(4)} {this.renderSquare(5)} </div> <div className="board-row"> {this.renderSquare(6)} {this.renderSquare(7)} {this.renderSquare(8)} </div> </div> ); } } class Game extends React.Component { constructor(props) { super(props); this.state = { history: [{ squares: Array(9).fill(null) }], stepNumber: 0, xIsNext: true, }; } handleClick(i) { const history = this.state.history.slice(0, this.state.stepNumber + 1); const current = history[history.length - 1]; const squares = current.squares.slice(); if (calculateWinner(squares) || squares[i]) { return; } squares[i] = this.state.xIsNext ? 'x' : '0'; this.setState({ history: history.concat([{ squares: squares, }]), stepNumber: history.length, xIsNext: !this.state.xIsNext, }); } jumpTo(step) { this.setState({ stepNumber: step, xIsNext: (step % 2) === 0, }); } render() { const history = this.state.history; const current = history[this.state.stepNumber]; const winner = calculateWinner(current.squares); const moves = history.map((step, move) => { const desc = move ? '移動' + move : 'ゲームスタート'; return ( <li key={move}> <button onClick={() => this.jumpTo(move)} >{desc}</button> </li> ); }); let status; if (winner) { status = '勝者:' + winner; } else { status = '次のプレイヤー:' + (this.state.xIsNext ? 'X' : '0'); } return ( <div className="game"> <div className="game-board"> <Board squares={current.squares} onClick={(i) => this.handleClick(i)} /> </div> <div className="game-info"> <div>{ status }</div> <ol>{ moves }</ol> </div> </div> ); } } // ======================================== ReactDOM.render( <Game />, document.getElementById('root') ); function calculateWinner(squares) { const lines = [ [0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6], ]; for (let i = 0; i < lines.length; i++) { const [a, b, c] = lines[i]; if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { return squares[a]; } } return null; }ゲームスタートで新規ゲーム開始
ゲーム途中に移動ができる
- 投稿日:2020-04-30T17:44:34+09:00
Reactチュートリアル ◯✖️ゲーム
Reactで作る ◯✖️ゲーム
公式サイト https://ja.reactjs.org/tutorial/tutorial.html
公式の最終コード https://codepen.io/gaearon/pen/gWWZgR?editors=0010変更するのは一つのファイル(index.js)だけ
デザインを変えたければindex.cssを変更するプロジェクトディレクトリ/src/index.jsimport React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; function Square(props) { return ( <button className="square" onClick={props.onClick} > {props.value} </button> ); } class Board extends React.Component { renderSquare(i) { return ( <Square value={this.props.squares[i]} onClick={() => this.props.onClick(i)} /> ); } render() { return ( <div> <div className="board-row"> {this.renderSquare(0)} {this.renderSquare(1)} {this.renderSquare(2)} </div> <div className="board-row"> {this.renderSquare(3)} {this.renderSquare(4)} {this.renderSquare(5)} </div> <div className="board-row"> {this.renderSquare(6)} {this.renderSquare(7)} {this.renderSquare(8)} </div> </div> ); } } class Game extends React.Component { constructor(props) { super(props); this.state = { history: [{ squares: Array(9).fill(null) }], stepNumber: 0, xIsNext: true, }; } handleClick(i) { const history = this.state.history.slice(0, this.state.stepNumber + 1); const current = history[history.length - 1]; const squares = current.squares.slice(); if (calculateWinner(squares) || squares[i]) { return; } squares[i] = this.state.xIsNext ? 'x' : '0'; this.setState({ history: history.concat([{ squares: squares, }]), stepNumber: history.length, xIsNext: !this.state.xIsNext, }); } jumpTo(step) { this.setState({ stepNumber: step, xIsNext: (step % 2) === 0, }); } render() { const history = this.state.history; const current = history[this.state.stepNumber]; const winner = calculateWinner(current.squares); const moves = history.map((step, move) => { const desc = move ? '移動' + move : 'ゲームスタート'; return ( <li key={move}> <button onClick={() => this.jumpTo(move)} >{desc}</button> </li> ); }); let status; if (winner) { status = '勝者:' + winner; } else { status = '次のプレイヤー:' + (this.state.xIsNext ? 'X' : '0'); } return ( <div className="game"> <div className="game-board"> <Board squares={current.squares} onClick={(i) => this.handleClick(i)} /> </div> <div className="game-info"> <div>{ status }</div> <ol>{ moves }</ol> </div> </div> ); } } // ======================================== ReactDOM.render( <Game />, document.getElementById('root') ); function calculateWinner(squares) { const lines = [ [0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6], ]; for (let i = 0; i < lines.length; i++) { const [a, b, c] = lines[i]; if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { return squares[a]; } } return null; }ゲームスタートで新規ゲーム開始
ゲーム途中に移動ができる
- 投稿日:2020-04-30T17:44:01+09:00
【第7回】教示と練習課題 [jsPsych]
はじめに
本記事は,「jsPsychによる心理学実験作成チュートリアル」の第7回の記事です。第6回では参加者情報の取得方法を紹介しました。そこで実験課題は完成しましたが,もう少し実験プログラムを作り込んで行きます。今回は課題の教示,課題の練習をこれまでのコードに追加します。
このチュートリアルシリーズの目的・概要等が気になった方はこちらの全体のまとめをご一読ください。
教示・長文テキスト
参加者が課題を行うにあたって,どのように進めればいいか説明する必要があります。といっても,これまで使い続けてきた
html-keyboard-responseを使えばいいだけです1。show_instruction.html<!DOCTYPE html> <html> <head> <script src="../jspsych.js"></script> <script src="../plugins/jspsych-html-keyboard-response.js"></script> <link rel="stylesheet" href="../css/jspsych.css"></link> </head> <body></body> <script> var instruction = { type: 'html-keyboard-response', stimulus: '教示です', } jsPsych.init({ timeline: [instruction], }); </script> </html>このコードについて特に説明することはありません。これを課題の前に入れれば,教示に続けて課題が実行されます。しかしながら,「教示です」なんて教示はありません。もっと課題の概要・取り組み方を説明しなければなりません。
本シリーズで作成してきたサイモン課題は,まず「キー押しの課題です」。具体的には,「LとRがランダムに提示されます」。そして,参加者がしなければならないのは,提示された刺激が「『L』なら左手人差し指で f キーを押し,『R』なら右手人差し指で j キーを押す」ということです。適切に改行しながらこれを反映すると,
stimulus:の部分は以下のようになります。<br>で改行することができます。var instruction = { type: 'html-keyboard-response', stimulus: 'これからキー押し課題を行ってもらいます。<br>「L」「R」がランダムに表示されるので,<br><br>「L」が表示されたら左手人差し指で f キー<br>「R」が表示されたら右手人差し指で j キー<br><br> を押してください。<br>準備ができたらいずれかのキーを押して課題を始めてください。', }これで教示としてはOKになりました。改行を2つ重ねていると空白行ができるので,段落などのまとまりを作って,見やすくしたりすることができます。しかし,実験を作っている立場からすると,1行で書くのは見にくくて不便です。
解決方法の一つは,いくつかの文字列に分割して,
+で連結するようにします。+を使っている部分ではコード上で改行することができるので,コードを見やすくすることができます。var instruction = { type: 'html-keyboard-response', stimulus: 'これからキー押し課題を行ってもらいます。<br>' + '「L」「R」がランダムに表示されるので,<br><br>' + '「L」が表示されたら左手人差し指で f キー<br>'+ '「R」が表示されたら右手人差し指で j キー<br><br>' + 'を押してください。<br>' + '準備ができたらいずれかのキーを押して課題を始めてください。', }課題の練習
実際の実験では,分析用のデータを取る(つまり本番)の前に,練習課題を行います。参加者の教示の理解を確認したり,反応のマッピングを生成したりするために重要です。そこで,練習課題用のブロックも作成しましょう。本番と異なるのは試行数だけなので,別々のリピート数で生成した
trial_types_practiceとtrial_types_mainを作成して,それぞれをtimeline_variablesに指定した変数を作成すれば大丈夫です。色々を省略しますが,以下のように書けます。var trial_types = [ {letter: 'L', pos: 'left', cond: 'cong'}, {letter: 'R', pos: 'left', cond: 'incong'}, {letter: 'L', pos: 'right', cond: 'incong'}, {letter: 'R', pos: 'right', cond: 'cong'}, ]; var trial = { type: 'html-keyboard-response', // 省略 } // 練習と本番で別々のtimeline_variablesを作成する var trial_types_practice = jsPsych.randomization.repeat(trial_types, 2); var trial_types_main = jsPsych.randomization.repeat(trial_types, 10); // それぞれをtimeline_variablesに指定したブロックを作成する var simon_practice = { timeline_variables: trial_types_practice, timeline:[trial] } var simon_main = { timeline_variables: trial_types_main, timeline: [trial], } jsPsych.init({ timeline: [simon_practice, simon_main], // データの保存とか });基本的にこれでOKです。分析の際は,
internal_node_idの列を参照すれば本番の試行だけを取り出すことが可能です。もし,より明示的にどの部分が課題の練習/本番なのかをデータに残しておきたいのであれば,少し冗長にはなりますが,以下のように書けばいいでしょう。
コード例
// 練習と本番で別々のtimeline_variablesを作成する var trial_types_practice = [ {letter: 'L', pos: 'left', cond: 'cong', phase: 'practice'}, {letter: 'R', pos: 'left', cond: 'incong', phase: 'practice'}, {letter: 'L', pos: 'right', cond: 'incong', phase: 'practice'}, {letter: 'R', pos: 'right', cond: 'cong', phase: 'practice'}, ]; var trial_types_main = [ {letter: 'L', pos: 'left', cond: 'cong', phase: 'main'}, {letter: 'R', pos: 'left', cond: 'incong', phase: 'main'}, {letter: 'L', pos: 'right', cond: 'incong', phase: 'main'}, {letter: 'R', pos: 'right', cond: 'cong', phase: 'main'}, ]; var trial = { type: 'html-keyboard-response', // 省略 data: { letter: jsPsych.timelineVariable('letter'), pos: jsPsych.timelineVariable('pos'), cond: jsPsych.timelineVariable('cond'), phase: jsPsych.timelineVariable('phase') // phase情報を追加する }, // 省略 } trial_types_practice = jsPsych.randomization.repeat(trial_types_practice, 2); trial_types_main = jsPsych.randomization.repeat(trial_types_main, 10); // それぞれをtimeline_variablesに指定したブロックを作成する var simon_practice = { timeline_variables: trial_types_practice, timeline:[trial] } var simon_main = { timeline_variables: trial_types_main, timeline: [trial], } jsPsych.init({ timeline: [simon_practice, simon_main], // データの保存とか });サイモン課題の完成
教示・練習もできたので,あとは注視点と合わせてこれまでのコードに付け足せば,サイモン課題は完成です。
コード例
simon.html<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="../jspsych.js"></script> <script src="../plugins/jspsych-html-keyboard-response.js"></script> <script src="../plugins/jspsych-survey-text.js"></script> <script src="../plugins/jspsych-survey-multi-choice.js"></script> <link rel="stylesheet" href="../css/jspsych.css"></link> <style> .text_left { position: absolute; left: 40%; top: 50%; transform: translateY(-50%) translateX(-50%); -webkit-transform: translateY(-50%) translateX(-50%); } .text_right { position: absolute; right: 40%; top: 50%; transform: translateY(-50%) translateX(50%); -webkit-transform: translateY(-50%) translateX(50%); } </style> </head> <body></body> <script> // 参加者情報の取得 var par_info = {}; var par_id = { type: 'survey-text', questions: [ {prompt: '参加者IDを入力してください', columns: 10, required: true, name: 'participantID'}, ], button_label: '次へ', on_finish: function(data) { par_info.id = JSON.parse(data.responses).participantID // 一時保存 } }; var age = { type: 'survey-text', questions: [ {prompt: '年齢を入力してください', columns: 3, required: true, name: 'age'}, ], button_label: '次へ', on_finish: function(data) { par_info.age = JSON.parse(data.responses).age // 一時保存 } }; var gender = { type: 'survey-multi-choice', questions: [ {prompt: "性別を選択してください", options: ['男性', '女性'], required: true, horizontal: true, name: 'gender'}, ], button_label: '次へ', on_finish: function(data) { par_info.gender = JSON.parse(data.responses).gender // 一時保存 } }; // 教示 var instruction = { type: 'html-keyboard-response', stimulus: 'これからキー押し課題を行ってもらいます。<br>' + '「L」「R」がランダムに表示されるので,<br><br>' + '「L」が表示されたら左手人差し指で f キー<br>'+ '「R」が表示されたら右手人差し指で j キー<br><br>' + 'を押してください。<br>' + '準備ができたらいずれかのキーを押して課題を始めてください。', } var go_practice = { type: 'html-keyboard-response', stimulus: 'まず課題の練習を行います。<br>' + '左手と右手の人差指を それぞれ f, j キーの上においてください。<br><br>' + '準備ができたらいずれかのキーを押して課題を始めてください。', post_trial_gap: 1000 } var go_main = { type: 'html-keyboard-response', stimulus: 'まず本番を行います。<br>' + '左手と右手の人差指を それぞれ f, j キーの上においてください。<br><br>' + '準備ができたらいずれかのキーを押して課題を始めてください。', post_trial_gap: 1000 } // 注視点 var fixation = { type: 'html-keyboard-response', stimulus: "+", choices: jsPsych.NO_KEYS, trial_duration: 1000, post_trial_gap: 1000, } // サイモン課題 var trial_types = [ {letter: 'L', pos: 'left', cond: 'cong'}, {letter: 'R', pos: 'left', cond: 'incong'}, {letter: 'L', pos: 'right', cond: 'incong'}, {letter: 'R', pos: 'right', cond: 'cong'}, ]; var trial = { type: 'html-keyboard-response', stimulus: function() { return '<div class="text_' + jsPsych.timelineVariable('pos', true) + '">' + jsPsych.timelineVariable('letter', true) + '</div>' }, choices: ['f', 'j'], trial_duration: 1000, post_trial_gap: 1000, // ITI data: { letter: jsPsych.timelineVariable('letter'), pos: jsPsych.timelineVariable('pos'), cond: jsPsych.timelineVariable('cond') }, on_finish: function(data) { data.key = jsPsych.pluginAPI.convertKeyCodeToKeyCharacter(data.key_press) if (data.letter == 'L') { data.correct = Number(data.key == 'f') } else { data.correct = Number(data.key == 'j') } } } trial_types_practice = jsPsych.randomization.repeat(trial_types, 2); trial_types_main = jsPsych.randomization.repeat(trial_types, 10); // それぞれをtimeline_variablesに指定したブロックを作成する var simon_practice = { timeline_variables: trial_types_practice, timeline:[trial] } var simon_main = { timeline_variables: trial_types_main, timeline: [trial], } jsPsych.init({ timeline: [par_id, age, gender, instruction, go_practice, fixation, simon_practice, go_main, fixation, simon_main], on_finish: function() { jsPsych.data.addProperties(par_info); jsPsych.data.get().localSave('csv', 'data.csv'); }, }); </script> </html>試行間隔の時間(ITI)を設けるために,これまでずっと
jsPsych.init({})にdefault_itiという項目を設けていましたが,これを削除して,適宜必要な部分にpost_trial_gapという項目を設定しています(具体的には,go_practice,go_main,fixation,trial)。default_itiだと画面が遷移するたびに指定した時間だけ空白の画面が挿入されます。しかし,参加者情報の入力などでは必要ないので,必要な部分だけにITIを設けるようにしました。おわりに
これでサイモン課題実験が完成しました。お疲れ様です。ぜひ,身近な人に実施してみてください。
もちろん,チュートリアルということで,jsPsychの機能のほんの一部しか紹介していません。ウィンドウの全画面表示の方法すら紹介していません。しかし,ここまでやり遂げたみなさんなら,ダウンロードしたjsPsychに同梱されているexampleコードや,jsPsychの公式リファレンスを見ながら,ご自身の研究課題に合わせてコードを書くことができるはずです。
これからもがんばってください。
ただし,教示文の改行や配置(左寄せ・中央寄せ)にこだわりがあったり,図なども教示に表示したいという場合は,パワーポイントなどで教示用の画像を作って
image-keyboard-responseで提示させたほうが楽かもしれません。htmlなので,自由なレイアウトが可能ですが使い方を勉強する必要があります。 ↩
- 投稿日:2020-04-30T17:42:28+09:00
高校生に触発された高校生による高校生のためのカレンダー&給与計算Webアプリ
はじめに
数週間前、Qiitaでふと目にした記事に触発されてタイトルのようなWebアプリを作ろうと決めました。触発されたと言えばかっこいいけど、それまで友達を喜ばせたい一心でアダルト検索サイトを作っていた自分が情けなくなっただけのことです。
なんというか、僕と年はそう変わらないのに誰かのためになろうとし、それを3日で実現してしまう行動力が凄いなと。(語彙力)
※タイトルで「高校生のための」と書きましたが、Payroxは全てのパートタイム従業員向けのWebアプリです。高校生以外の方もぜひ使ってみてください。
仕様
僕が作ったWebアプリ"Payrox"はカレンダー機能と給与計算が混じったWebアプリで、Googleカレンダーに給与計算機能がついたものと考えていただければ嬉しいです。計算システムは自作のモジュールに全て任せているので、そちらの記事も読んでいただけると幸いです。
実際にはPWA、Googleカレンダー、固定費用計算以外は実現できたのでまあまあ満足しています。
苦労したところ
ライブラリが動いてくれない
今回Payroxを作るにあたって、Fullcalendarというライブラリを使用しました。カレンダーライブラリの中でもかなり有力なもので、とにかく実装が簡単なことが売りなんですが、簡単にできる実装というのは実はあまり多くなくサーバーとのやり取りなど動的な操作は意外と難しかったりするんです。カレンダーに予定を入力する動作一つとっても日本語の情報は少なく、英語のドキュメントを理解しなければならなかったので、日本語ドキュメントであれば数時間で終わるところに2日かけたり、データを保存するためにコンソール画面の見たこともないような画面と睨めっこしたりと、よく言えば勉強になるライブラリでした。
参考にした記事
ログインか、LocalStorageか
上の仕様スクリーンショットにも書いてある通り、カレンダー機能ですからデータ保存は必須で、ログイン機能を実装するかログイン無しでも使えるLocalStorageを使うかの2択でかなり迷いました。普通に考えて、同期できるログイン機能の方が圧倒的に優れているんですが、LocalStorageの方がユーザーがとっつきやすそう、そして実装が簡単そうという理由からつい、ついLocalStorageを選んでしまいました。。。後々ログイン機能は実装します。
カレンダーを一番上に!
Googleカレンダーアプリを開くと、真っ先にカレンダーが目に入ります。カレンダーアプリだから当たり前なんですが、オプションなどはサイドメニューにあり、画面すれすれにイベント挿入ボタンを配置しています。
ネイティブアプリとWebアプリの大きな違いの一つとして、画面一杯を自由に使えるかどうかという違いがあります。ネイティブアプリは自分の好きなように画面スレスレにボタンを配置できますが、Webアプリの場合、余裕を持たせないと画面下のタブに隠れてしまうので、実際に使える画面のサイズは少し小さくなります。他のWebアプリでは大した影響は出ないかもしれませんが、カレンダーアプリにおいてはその少しの違いがユーザビリティに大きく影響を与えることが多いのです。
さらに今回の場合、僕のお財布の問題により無料で使える代わりに広告表示が必須のXREAというレンタルサーバーを使うことになったので、その分カレンダーが画面の下に追いやられることになりました。(XREAの広告はスクロールせずに見られる位置に置かなければならない)
結局、ヘッダーとXREAの広告の真下にmargin,paddingをギリギリまで減らしてカレンダーを配置することで、スマホ版でも何とかスクロール無しでカレンダーを見られるようにしましたが、Swiftを習得した暁にはもっと使いやすいアプリを作る予定です。。。おわりに
今回作ったPayroxはGitHubでソースコードを公開しています。
https://github.com/kota-yata/Payrox
何せ未熟者なので、コード的にはかなり非効率な部分があると思います。issue、プルリク、リファクタリングなど投げていただけるとめっちゃ嬉しいです。もう一度言います。GitHubで絡んでくれるとめっちゃ嬉しいです。
まだまだYobikake.comの人には及ばないけども、参考になれば幸いです。
- 投稿日:2020-04-30T17:39:49+09:00
【第6回】参加者情報の取得 [jsPsych]
はじめに
本記事は,「jsPsychによる心理学実験作成チュートリアル」の第6回の記事です。第5回では実験データの保存方法を紹介しました。今回は,参加者情報の取得について紹介します。
このチュートリアルシリーズの目的・概要等が気になった方はこちらの全体のまとめをご一読ください。
参加者情報の取得
さっそく参加者情報を取得していきましょう。今回のチュートリアルで収集する情報は,参加者ID,年齢,性別の3つとします。参加者IDと年齢は
jspsych-survey-text,性別はjspsych-survey-multi-choiceというプラグインを使って収集していきます。まずは以下のコードを実行してみてください。get_participant_info.html<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <!-- これを足す --> <script src="../jspsych.js"></script> <script src="../plugins/jspsych-survey-text.js"></script> <script src="../plugins/jspsych-survey-multi-choice.js"></script> <link rel="stylesheet" href="../css/jspsych.css"></link> </head> <body></body> <script> var par_id = { type: 'survey-text', questions: [ {prompt: '参加者IDを入力してください', columns: 10, required: true, name: 'participantID'}, ], button_label: '次へ', }; var age = { type: 'survey-text', questions: [ {prompt: '年齢を入力してください', columns: 3, required: true, name: 'age'}, ], button_label: '次へ', }; var gender = { type: 'survey-multi-choice', questions: [ {prompt: "性別を選択してください", options: ['男性', '女性'], required: true, horizontal: true, name: 'gender'}, ], button_label: '次へ', }; jsPsych.init({ timeline: [par_id, age, gender], on_finish: function() { jsPsych.data.get().localSave('csv', 'data.csv'); }, }); </script> </html>全部で3ページの実験(?)プログラムが実行されたはずです。まず,本題とは逸れますが,質問文に日本語を使っているので,
<head>の直後に<meta charset="utf-8">が挿入されています(第1回参照)。さて,本題です。
survey-textとsurvey-multi-choiceのどちらもquestions:がメインの設定項目になります。どちらもquestions:[]と[]内に質問の内容や解答欄・選択肢を設定するための連想配列を持ってきますが,その連想配列内で設定できる内容がtextとmulti-choiceで少し異なります。
survey-textの場合,columnsという設定ができます。これは,回答を入力するためのテキストボックスの幅を決める項目で,数字が大きいほど幅が広くなります。適宜,想定される回答の長さに応じて変更してください。
survey-multi-choiceの場合,optionsという設定ができます。これは,選択肢を指定する項目で,例のように,配列を使います。また,horizontalという設定もあります。これは選択肢を横(horizontal)に並べるか,縦に並べるかどうかを指定できます。horizontal: trueで横に並べることができます。また,この項目を設定しなければ,jsPsychは自動的に選択を縦に並べます。残りは共通の設定で,
promptには質問の内容,requiredでは回答を必須とするかどうか,nameには回答がデータに保存されるときの名前を指定しています。requiredで回答を必須にしていないと無回答で次に進めてしまうので,参加者情報を集める場合に限っては常にtrueでいいでしょう。なお,
questionsは複数形になっている通り,[]内に質問用の連想配列を複数持ってくることができます。その場合,1つのページに2つ以上の質問を提示することができます。適宜利用してください。本チュートリアルでは参加者IDと年齢を尋ねる質問は別々のページで提示することにします。var parID_age = { type: 'survey-text', questions: [ {prompt: '参加者IDを入力してください', columns: 10, required: true, name: 'participantID'}, {prompt: '年齢を入力してください', columns: 3, required: true, name: 'age'}, ], button_label: '次へ', };回答の保存方法をアレンジする
次に,回答が保存されたファイルを確認してみましょう。
responsesという列に回答が保存されていますが,困ったことに{"participantID":"kla"}のように連想配列の形式で保存されてしまっています。これでは分析しにくいので,回答の部分だけを取り出して別の列に保存する方法を考えていきましょう。個人的に一番シンプルだと思う方法は,回答が終了したタイミングで反応を取り出しておくというものです。前回,
on_finishという項目を設けて,on_finish: function(data) {...}とすることで,各試行の終了時にその試行で得られたデータにアクセスし,回答の正誤などを判定する方法を紹介しました。これと同様の方法を用います。具体的には,以下のようにします。var par_info = {}; var par_id = { type: 'survey-text', questions: [ {prompt: '参加者IDを入力してください', columns: 10, required: true, name: 'participantID'}, ], // ここから on_finish: function(data) { par_info.id = JSON.parse(data.responses).participantID } // ここまで };まず,
JSON.parse(data.responses).participantIDについて説明します。data.列名で保存された特定のデータにアクセスできることは前回紹介したとおりです。先ほど保存されたファイルを確認した際に,回答はresponsesという名前の列に保存されていたので,data.responsesで回答を取り出すことができます。しかしながら,この時点ではdata.responsesに保存されている回答はjsonという形式になっています。これをjavascriptの連想配列に変換するために,JSON.parse()を使います。ここでようやく{participantID:"kla"}という連想配列が得られます。連想配列.キー名でそのキーに対応する値を取り出すことができるので,.participantIDで"kla"という文字列を得ることができます。このキー名は,質問項目の設定name:で指定した文字列に一致します。次に,
par_info.id =について説明します。ここでは,参加者IDを事前に作った連想配列var par_info = {}に一時保存しています。前回の記事では,on_finish: function(data) {...}の中でdata.新しい列名 = 値とすることで,任意の値を新しい列に保存できることを紹介しました。同様に今回も,data.id = JSON.parse(...)として保存しても構わないのですが,その場合参加者IDはたった1行にしか保存されません(試してみてください)。参加者ID,性別などの参加者情報はデータのすべての行にあるほうが分析がしやすい場合が多いので,実験の最後に「すべての行に参加者情報を保存する」という処理が実行できるように,ここでは別の連想配列に一時保存しています。上記のコードを少しずつ変更して,他の参加者情報も一時保存するコードは以下のようになります。ここでは合わせて,最後に一時保存した参加者情報をすべての行に追加して保存しています。
save_participant_info.html<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <!-- これを足す --> <script src="../jspsych.js"></script> <script src="../plugins/jspsych-survey-text.js"></script> <script src="../plugins/jspsych-survey-multi-choice.js"></script> <link rel="stylesheet" href="../css/jspsych.css"></link> </head> <body></body> <script> var par_info = {}; var par_id = { type: 'survey-text', questions: [ {prompt: '参加者IDを入力してください', columns: 10, required: true, name: 'participantID'}, ], button_label: '次へ', on_finish: function(data) { par_info.id = JSON.parse(data.responses).participantID // 一時保存 } }; var age = { type: 'survey-text', questions: [ {prompt: '年齢を入力してください', columns: 3, required: true, name: 'age'}, ], button_label: '次へ', on_finish: function(data) { par_info.age = JSON.parse(data.responses).age // 一時保存 } }; var gender = { type: 'survey-multi-choice', questions: [ {prompt: "性別を選択してください", options: ['男性', '女性'], required: true, horizontal: true, name: 'gender'}, ], button_label: '次へ', on_finish: function(data) { par_info.gender = JSON.parse(data.responses).gender // 一時保存 } }; jsPsych.init({ timeline: [par_id, age, gender], on_finish: function() { jsPsych.data.addProperties(par_info); // すべての行に追加 jsPsych.data.get().localSave('csv', 'data.csv'); }, }); </script> </html>終盤にある
jsPsych.data.addProperties()という関数が,すべての行に参加者情報を追加している部分になります。保存されたcsvファイルを確認すると,par_info.id,.age,.genderのそれぞれid,age,genderが列名になっていて,それらの列のすべての行に,回答が保存されていると思います。timeline_variablesを使ってみる(余談)
先ほどのコードでは,参加者情報を集めるために
par_id,age,genderと3つの試行変数を作りました。しかし,それぞれの中身の共通部分は多いので,timeline_variablesを使ってまとめてしまいましょう。紹介はしますが,必須ではないです。
コード例
get_info_timeline_variables.html<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <!-- これを足す --> <script src="../jspsych.js"></script> <script src="../plugins/jspsych-survey-text.js"></script> <script src="../plugins/jspsych-survey-multi-choice.js"></script> <link rel="stylesheet" href="../css/jspsych.css"></link> </head> <body></body> <script> var question_settings = [ {type: 'survey-text', colname: 'ID', question: {prompt: '参加者IDを入力してください', columns: 10, required: true}}, {type: 'survey-text', colname: 'age', question: {prompt: '年齢を入力してください', columns: 3, required: true}}, {type: 'survey-multi-choice', colname: 'gender', question: {prompt: "性別を選択してください", options: ['男性', '女性'], required: true, horizontal: true}}, ]; var par_info = {}; var get_info = { type: jsPsych.timelineVariable('type'), questions: [jsPsych.timelineVariable('question')], button_label: '次へ', on_finish: function(data) { var colname = jsPsych.timelineVariable('colname', true); par_info[colname] = JSON.parse(data.responses).Q0 } }; jsPsych.init({ timeline: [{ timeline_variables: question_settings, timeline: [get_info], }], on_finish: function() { jsPsych.data.addProperties(par_info); // すべての行に追加 jsPsych.data.get().localSave('csv', 'data.csv'); }, }); </script> </html>
type,questionsの中身,colname(保存時の列名)が質問内容によって変わるので,各ページでそれらが変更されるように,question_settingsというtimeline_variablesを作っています。一つ前の例のコードと見比べてどのようにjsPsych.timelineVariableを使っているかを確認してください。2点,これまで説明していなかったものがあるので,それについて説明します。どちらも
par_info[colname] = JSON.parse(data.responses).Q0に関わるものです
par_info[colname] =とあるように,連想配列への要素の追加はpar_info.キー名だけでなく,par_info[キー名]でも行うことができます。- 各質問内容の設定(
question_settignsのquestion:)でname:を指定していません。指定しない場合,デフォルトの設定が反映され回答は{Q0: 回答}と保存されます。そうすれば,どの質問でも,JSON.parse(data.responses).Q0で回答を取り出せるようになります。name:を指定してしまうと,この.Q0の部分が指定された名前に変更されてしまうため,ここにもjsPsych.timelineVariable()を使う必要がでてきます。これまでのコードに取り入れる
最後に,今回の記事で紹介したコードをこれまでのコードと合体させます。基本的には付け足していけばいいですが,
jsPsych.init({})内にあるtimeline:[]の部分には少し変更が必要です。前回までの記事では,以下のようにしていました。jsPsych.init({ timeline: [{ timeline_variables: trial_types, timeline: [trial], }], // 以下省略... })ここに,
par_id,age,genderを追加していく必要があります。以下のようにすればいいのですが,jsPsych.init({ timeline: [par_id, age, gender, { timeline_variables: trial_types, timeline: [trial], }], // 以下省略... })このままだと外側の
timeline:[]の中身が見にくくなってしまいます。実は,{timeline_variables: ..., timeline: ...}の部分は事前に別のところで変数として作っておくことができます。この部分は,サイモン課題に対応しているので,下の例ではsimonという名前をつけています。var simon = { timeline_variables: trial_types, timeline: [trial], } jsPsych.init({ timeline: [par_id, age, gender, simon], // 以下省略... })これで,実験の流れがかなり見やすくなったと思います。すべてまとめたコードは以下を参照してください。
コード例
get_participant_info_all.html<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="../jspsych.js"></script> <script src="../plugins/jspsych-html-keyboard-response.js"></script> <script src="../plugins/jspsych-survey-text.js"></script> <script src="../plugins/jspsych-survey-multi-choice.js"></script> <link rel="stylesheet" href="../css/jspsych.css"></link> <style> .text_left { position: absolute; left: 40%; top: 50%; transform: translateY(-50%) translateX(-50%); -webkit-transform: translateY(-50%) translateX(-50%); } .text_right { position: absolute; right: 40%; top: 50%; transform: translateY(-50%) translateX(50%); -webkit-transform: translateY(-50%) translateX(50%); } </style> </head> <body></body> <script> // 参加者情報の取得 var par_info = {}; var par_id = { type: 'survey-text', questions: [ {prompt: '参加者IDを入力してください', columns: 10, required: true, name: 'participantID'}, ], button_label: '次へ', on_finish: function(data) { par_info.id = JSON.parse(data.responses).participantID // 一時保存 } }; var age = { type: 'survey-text', questions: [ {prompt: '年齢を入力してください', columns: 3, required: true, name: 'age'}, ], button_label: '次へ', on_finish: function(data) { par_info.age = JSON.parse(data.responses).age // 一時保存 } }; var gender = { type: 'survey-multi-choice', questions: [ {prompt: "性別を選択してください", options: ['男性', '女性'], required: true, horizontal: true, name: 'gender'}, ], button_label: '次へ', on_finish: function(data) { par_info.gender = JSON.parse(data.responses).gender // 一時保存 } }; // サイモン課題 var trial_types = [ {letter: 'L', pos: 'left', cond: 'cong'}, {letter: 'R', pos: 'left', cond: 'incong'}, {letter: 'L', pos: 'right', cond: 'incong'}, {letter: 'R', pos: 'right', cond: 'cong'}, ]; var trial = { type: 'html-keyboard-response', stimulus: function() { return '<div class="text_' + jsPsych.timelineVariable('pos', true) + '">' + jsPsych.timelineVariable('letter', true) + '</div>' }, choices: ['f', 'j'], // 入力キーの指定 trial_duration: 1000, // 試行の持続時間 data: { letter: jsPsych.timelineVariable('letter'), pos: jsPsych.timelineVariable('pos'), cond: jsPsych.timelineVariable('cond') }, on_finish: function(data) { data.key = jsPsych.pluginAPI.convertKeyCodeToKeyCharacter(data.key_press) if (data.letter == 'L') { data.correct = Number(data.key == 'f') } else { data.correct = Number(data.key == 'j') } } } var trial_types = jsPsych.randomization.repeat(trial_types, 2); var simon = { timeline_variables: trial_types, timeline: [trial], } jsPsych.init({ timeline: [par_id, age, gender, simon], default_iti: 250, on_finish: function() { jsPsych.data.addProperties(par_info); jsPsych.data.get().localSave('csv', 'data.csv'); }, }); </script> </html>おわりに
今回は参加者情報の取得について紹介しました。次回は課題の教示(と注視点)です。
- 投稿日:2020-04-30T17:36:10+09:00
Nuxt.js + Socket.io のつかいかたメモ
どどんてぃ のリファクタリングにあたりフレームワークをNuxtに変えようと思ってタイトルのあれこれを調べてみたらなんか全然うまく行かなかったので、素朴に socket.ioの公式ドキュメントを見ながら実装したらあっさりうまくいきましたってお話。公式ドキュメント大事。
初期設定
npx create-nuxt-app projectNameからプロジェクト作るわけですが、
- サーバーサイドのフレームワークはExpressにしておく
- レンダリングの方法はユニバーサルレンダリングにしておく
がやりやすいかと。あとはお好きに。無事プロジェクトフォルダが完成したら
cd projectName npm install --save socket.io npm install --save socket.io-clientと socket.io 関連のライブラリをインストール
サーバーサイド
適当に動作確認ができればいいので改変は最小限。
server/index.jsconst express = require('express') const consola = require('consola') const { Nuxt, Builder } = require('nuxt') const app = express() // socket.io の require const socket = require('socket.io') // Import and Set Nuxt.js options const config = require('../nuxt.config.js') config.dev = process.env.NODE_ENV !== 'production' async function start () { // Init Nuxt.js const nuxt = new Nuxt(config) const { host, port } = nuxt.options.server await nuxt.ready() // Build only in dev mode if (config.dev) { const builder = new Builder(nuxt) await builder.build() } // Give nuxt middleware to express app.use(nuxt.render) // Listen the server // サーバ情報を保存しておく const server = app.listen(port, host) consola.ready({ message: `Server listening on http://${host}:${port}`, badge: true }) // ソケットの作成 const io = socket(server) // 接続された時の処理 io.on('connection', (socket) => { // 適当にイベントを送り返してみる socket.emit('news', { msg: 'hello!' }) // イベントを受信した時の処理定義 socket.on('ev', (msg) => { console.log('ev', msg) }) }) } start()クライアントサイド
pages/index.vue<template> <div class="container"> <div> <logo /> <h1 class="title"> projectName </h1> <h2 class="subtitle"> description </h2> <div class="links"> <a href="https://nuxtjs.org/" target="_blank" class="button--green" > Documentation </a> <a href="https://github.com/nuxt/nuxt.js" target="_blank" class="button--grey" > GitHub </a> <!-- イベント送信のためのボタンを追加 --> <a class="button--grey" @click="sendEve" > sendEv </a> </div> </div> </div> </template> <script> // クライアントライブラリのインポート import io from 'socket.io-client' import Logo from '~/components/Logo.vue' export default { components: { Logo }, data () { return { socket: io() } }, // マウントされた時に初期化をする mounted () { this.socket.on('news', (data) => { console.log(data) }) }, methods: { sendEve () { this.socket.emit('ev', 'hogehoge') } } } </script> <style> (略) </style>これでサーバサイドとクライアントサイドのコンソールを見ながら、イベントの送受信が確認できればオッケー。
- 投稿日:2020-04-30T17:31:43+09:00
【第5回】データの保存 [jsPsych]
はじめに
本記事は,「jsPsychによる心理学実験作成チュートリアル」の第5回の記事です。第4回では
timelineVariablesの使い方を紹介しました。今回は,実験データを手元のPCに保存する方法を紹介します。残念ながら,今回紹介する保存方法は,オンライン実験を実施する際には利用できません。また,このチュートリアルではオンライン実験でのデータの保存方法を紹介しません。利用するサーバー(jsPsychがアップロードされていて,参加者がアクセスする場所)によってその方法は異なるからです。jsPsychの公式サイトでも一部のサーバーに利用できる保存方法を紹介してあるので,これからサーバーを借りてオンライン実験を実施するという場合は,掲載されている方法をそのまま利用できるサーバーを探してみるといいかもしれません。オンライン実験でのデータの保存方法もjsPsychの重要な要素ですが,それ以外にもjsPsychでのデータの扱い方について知っておくことはあるので,本記事では合わせてそれらを紹介します。
このチュートリアルシリーズの目的・概要等が気になった方はこちらの全体のまとめをご一読ください。
データを手元のPCに保存する
データを手元のPCに保存する方法は非常にシンプルです。
jsPsych.init()の中に新たにon_finishに続く3行を足せばいいだけです。jsPsych.init({ timeline: [{ timeline_variables: trial_types, timeline: [trial], }], default_iti: 250, // ここから on_finish: function() { jsPsych.data.get().localSave('csv', 'data.csv'); }, // ここまで });前回作成したコードに
on_finishの3行を追加して実行すると,課題の終了後,data.csvという名前のファイルが,ブラウザの設定にしたがって保存されます。デフォルトなら「ダウンロード」という名前のフォルダに自動で保存されていると思います。設定内容を確認する方法は,「ブラウザ名 + ファイル + ダウンロード先」とかでweb検索すると見つかるはずです。
on_finishという名前にあるように,終了時にfunction(){}内に入っている処理を実行しています。jsPsych.dataというところに保存されているデータを取り出し.get()手元のPCにcsv形式で「data.csv」という名前で保存するようにしています.localSave('csv', 'data.csv')。csvというのはcomma separated value(s)の略で,値(各データ)がカンマ,区切りで並べられて書かれているファイルです。対応しているアプリケーションも多く,エクセルのような表計算ソフトで開くことはもちろん可能ですし,PythonやRといったプログラミング言語でデータ分析をする際にも扱いやすいファイル形式です。さっそくデータの中身を確認してみましょう。かなり横に長くこのページでは見にくくなるので,ここには載せません。ちなみに,VS codeをお使いの方は「Rainbow CSV」という拡張機能をインストールするとVS Code上でもcsvの内容が確認しやすくなります。
さて,先程追加した3行以外に「〜〜のデータを保存する」ことを指定するコードは書いていませんが,さまざまなデータが保存されています。ただ厄介なのは,
stimulusとkey_pressです。stimulusには,実際に入力された形式のまま保存されます。前回紹介したtimelineVariblesの使い方のどの方法を用いたとしても,この<div>タグ付きの文字列が保存されることになります。これだと見にくいです。また,保存されたデータには各試行の条件を判別できる列がこれ以外にはないため,stimulusに保存されているデータから判定していくことになりますが,すこしテクニックが要りそうです。できることなら,(提示された文字と位置と)条件が最初からデータに保存されている状態にしておきたいです。
key_pressには,入力されたキーが保存されているはずですが,実際に入力したキーの文字(fやj)ではなく,数字が保存されています。これはバグっているわけではなく,保存されている数字はfとjに対応するキーコードです。第1回の「キー入力を指定する」というセクションでも軽く紹介しました。このままだと,分析の際にいちいちキーコードと対応する文字を確認する必要があるので放っておくのは不便です。(入力されたキーと)入力の正誤があるとその手間が省けて良さそうです。ということで,残りの部分では,提示された文字,位置,条件,入力されたキー,入力の正誤の5つを保存する方法を紹介します。
提示された文字,位置を保存する
第4回で紹介した,
trial_typesでletterとposを分ける方のコードに以下のように4行追加すれば,提示された文字と位置をデータに簡単に保存することができます。var trial_types = [ {letter: 'L', pos: 'left'}, {letter: 'R', pos: 'left'}, {letter: 'L', pos: 'right'}, {letter: 'R', pos: 'right'}, ]; var trial = { type: 'html-keyboard-response', stimulus: function(){ return '<div class="text_' + jsPsych.timelineVariable('pos', true) + '">' + jsPsych.timelineVariable('letter', true) + '</div>' }, choices: ['f', 'j'], trial_duration: 1000, // ここから data: { letter: jsPsych.timelineVariable('letter'), pos: jsPsych.timelineVariable('pos') }, // ここまで }この
data: {}の中にはtimelineVariable以外も入れることができます。以下のようにhogehoge: "gehogeho"を追加すると,サイモン課題のデータすべてに"gehogeho"という文字列が保存された列ができます。hogehoge: "gehogeho"は実用的ではありませんが,複数の課題を遂行させるような実験を実施する場合には,分析の際にどのデータがどの課題のものかを判別できるように,task: "simon"のようなデータを保存しておく必要がありますので,そのような際に活用してください。data: { letter: jsPsych.timelineVariable('letter'), pos: jsPsych.timelineVariable('pos'), hogehoge: "gehogeho", }各試行の条件を保存する
データに追加した文字
letterとpos情報から,各試行の条件を判定して,データとして保存されるようにしましょう。条件の判定にはif文を使用します。試行ごとにif文を実行してデータに保存するには,trial変数にもon_finish:という項目を追加します。if文
if文は以下のように書いて,ifの直後に指定されている条件が真
trueなら{}内の処理を実行します。if (条件式) { 何らかの処理 }例えば,以下のように書けます。
var a = 1; if (a == 1) { 処理A } var b = 2; if (b == 1) { 処理B }この例は,
aとbが1であればそれぞれ処理A,処理Bが実行されるというものです。==は等価演算子と呼ばれるもので,左右の値が等しいかどうかを判定し,等しければtrue,そうでなければfalseを返します。今回,aには1が入っているので,a == 1はtrueになり,処理Aは実行されます。一方で,bには2が入っているので,b == 1はfalseとなり処理Bは実行されません。
ifに合わせてelse if,elseというものも利用できます。まずelseについて紹介します。var b = 2; if (b == 1) { 処理B } else { 処理C }
elseを使うと,直前の条件式がfalseの場合に必ず実行する処理を記述することができます。上記の例であれば,b == 1はfalseなので,処理Cが実行されます。そして,
else ifは以下のように使うことができます。var b = 2; if (b == 1) { 処理B } else if (b == 3){ 処理C }
else ifは見た目の通り,elseにifを組み合わせたもので,直前の条件式がfalseの場合に()内の条件式の真偽を判定し,trueなら{}内の処理を実行します。上記の例であれば,b == 3はfalseになるので,処理Cも実行されません。
if,else,else ifは組み合わせて使うことができます。以下の例では,b == 1,b == 3のどちらもfalseになるので,処理Dが実行されます。var b = 2; if (b == 1) { 処理B } else if (b == 3){ 処理C } else { 処理D }今回のサイモン課題であれば,「『文字がL かつ 位置が左』または『文字がR かつ 位置が右』」なら一致条件,そうでなければ不一致条件ということになるので,それを判定する条件式は以下のように書けます。
condは条件conditionの最初の4文字です。if ((letter == 'L' && pos == 'left') || (letter == 'R' && pos == 'right')) { cond = 'cong' } else { cond = 'incong' }最初のifの条件式がかなり長いですが,「『文字がL かつ 位置が左』または『文字がR かつ 位置が右』」を変換しただけです。「かつ」が
&&,「または」が||になります。条件式を囲む()と,「Lかつ左」「Rかつ右」を囲む()があるので注意してください。横に長いのが嫌な人は,今回の例であれば,else ifを以下のように利用して横方向の長さを回避することができます。if (letter == 'L' && pos == 'left') { cond = 'cong' } else if (letter == 'R' && pos == 'right') { cond = 'cong' } else { cond = 'incong' }試行ごとに
if文を実行してデータに保存する今回の記事の一番初めに,実験データを保存するために
jsPsych.init()の中にon_finishという項目を設けて,実験終了時にデータを保存する処理が実行されるようにしました。実は,試行変数trialにもon_finishの項目は設け,各試行の最後に実行する処理を指定することができます。var trial = { type: 'html-keyboard-response', // stimulus: などは省略 data: { letter: jsPsych.timelineVariable('letter'), pos: jsPsych.timelineVariable('pos') }, // ここから on_finish: function() { 何らかの処理 } // ここまでそして,
on_finish: function(data){...}とすることで,{}内の処理でその試行変数のdataにアクセスできるようになります。今回は,各試行に提示された文字と位置を元にして条件を判定することが目標なので,{}内に先程作成したif文を代入すればいいわけです。dataに保存された文字letterと位置posにはdata.letter,data.posでアクセスすることができます1。ということで,それらを組み合わせると以下のようになります。
var trial = { type: 'html-keyboard-response', // stimulus: などは省略 data: { letter: jsPsych.timelineVariable('letter'), pos: jsPsych.timelineVariable('pos') }, // ここから on_finish: function(data) { if (data.letter == 'L' && data.pos == 'left') { data.cond = 'cong' } else if (data.letter == 'R' && data.pos == 'right') { data.cond = 'cong' } else { data.cond = 'incong' } } // ここまで }if文の例で
condだった部分もdata.condに変更しています。このように,dataに新しいデータを追加したい場合は,data.データ名 = データで追加することができます。if文を使わなくても良い
そもそも,上記のような面倒なことをしなくても,今回のサイモン課題程度なら
trial_types配列内の連想配列にcond: "cong"などを追加してしまうほうが,データに条件を追加するときにもjsPsych.timelineVariable('cond')と1行で済むので簡単です。var trial_types = [ {letter: 'L', pos: 'left', cond: 'cong'}, {letter: 'R', pos: 'left', cond: 'incong'}, {letter: 'L', pos: 'right', cond: 'incong'}, {letter: 'R', pos: 'right', cond: 'cong'}, ]; var trial = { type: 'html-keyboard-response', // stimulus: などは省略 data: { letter: jsPsych.timelineVariable('letter'), pos: jsPsych.timelineVariable('pos'), cond: jsPsych.timelineVariable('cond'), } // この場合はon_finishは要らない };入力されたキーの文字,正誤を保存する
入力されたキーはデータファイルの
key_pressの列に保存されていますが,それが実際の文字ではなく,キーコードで保存されていることは今回の記事の序盤に述べたとおりです。入力されたキーを文字で保存したい場合はこのキーコードを文字に変換しなければなりません。大変そうですが,実は,jsPsychにはjsPsych.pluginAPI.convertKeyCodeToKeyCharacter()という関数が用意されており,()内にキーコードを入れるだけで,対応する文字に変換してくれます。便利ですね。key_pressの列に保存されるデータには,on_finish: function(data){}の{}内でdata.key_pressとすることでアクセスすることができます2。また,data.任意の名前 = データで新しくデータを追加できることは直前のセクションで述べたとおりです。これらをまとめると,以下のようにして,入力されたキーの文字列を保存することができます。var trial = { type: 'html-keyboard-response', // stimulus: などは省略 // ここから on_finish: function(data) { data.key = jsPsych.pluginAPI.convertKeyCodeToKeyCharacter(data.key_press) } // ここまで }この例だと,
keyという列名でファイルに保存されることになります。このままの勢いで,入力されたキーの正誤判定までやってしまいましょう。あまりちゃんと説明していませんでしたが,今回のサイモン課題では,Lが提示されたら左手(人差し指)で
fキーを,Rが表示されたら右手(人差し指)でjキーを押すことが求められます。つまり,「提示された文字列がLのときはf,Rのときはj」が入力されていたら正反応ということになり,「そうでない場合」は誤反応ということになります。これまでの内容で実装することができそうです。コード例は下に記しますが,ぜひご自身でまずは挑戦してみてください。ちなみに,以下のコード例では,正なら1,誤なら0で保存しています。こうすれば,分析の際にデータの変換をせず,平均を取るだけで正反応率を算出できるからです。
コード例その1
var trial = { type: 'html-keyboard-response', // stimulus: などは省略 on_finish: function(data) { data.key = jsPsych.pluginAPI.convertKeyCodeToKeyCharacter(data.key_press) if (data.letter == 'L') { if (data.key == 'f') { data.correct = 1 } else { data.correct = 0 } } else { if (data.key == 'j') { data.correct = 1 } else { data.correct = 0 } } } }一番地道なコードです。if elseを繰り返し使うことになって
{}が増えて読みにくくなってしまうのが難点です。
コード例その2
var trial = { type: 'html-keyboard-response', // stimulus: などは省略 on_finish: function(data) { data.key = jsPsych.pluginAPI.convertKeyCodeToKeyCharacter(data.key_press) data.correct = 0 if (data.letter == 'L' && data.key == 'f') { data.correct = 1 } else if (data.leter == 'R' && data.key == 'j') { data.correct = 1 } } }どうせ
elseで0を保存するのなら,初めに0をdata.correctに入れておいて,正反応と判定されたら1に書き換えようという発想のコードです。elseだと3行使うのが,1行にまとまるので,省スペースですね。例その1と比べると,data.letter == 'L' && data.key = 'f'などを使ったことのほうが省スペースに貢献していますが。。。
コード例その3
var trial = { type: 'html-keyboard-response', // stimulus: などは省略 on_finish: function(data) { data.key = jsPsych.pluginAPI.convertKeyCodeToKeyCharacter(data.key_press) if (data.letter == 'L') { data.correct = Number(data.key == 'f') } else { data.correct = Number(data.key == 'j') } } }たぶん,この書き方が一番行数が少なくなると思います。
data.key == 'f'はtrueかfalseを返しますが,Number()で数字に変換すると,それぞれ1,0になります。いくつか例を載せましたが,他にも書き方はいくつかあると思います。とりあえず回ればどんなコードでもいいと思います。
おわりに
今回の記事の内容をこれまでのコードに追加すると以下のようになります。
コード例
save-data.html<!DOCTYPE html> <html> <head> <script src="../jspsych.js"></script> <script src="../plugins/jspsych-html-keyboard-response.js"></script> <link rel="stylesheet" href="../css/jspsych.css"></link> <style> .text_left { position: absolute; left: 40%; top: 50%; transform: translateY(-50%) translateX(-50%); } .text_right { position: absolute; right: 40%; top: 50%; transform: translateY(-50%) translateX(50%); } </style> </head> <body></body> <script> var trial_types = [ {letter: 'L', pos: 'left', cond: 'cong'}, {letter: 'R', pos: 'left', cond: 'incong'}, {letter: 'L', pos: 'right', cond: 'incong'}, {letter: 'R', pos: 'right', cond: 'cong'}, ]; var trial = { type: 'html-keyboard-response', stimulus: function(){ return '<div class="text_' + jsPsych.timelineVariable('pos', true) + '">' + jsPsych.timelineVariable('letter', true) + '</div>' }, choices: ['f', 'j'], // 入力キーの指定 trial_duration: 1000, // 試行の持続時間 data: { letter: jsPsych.timelineVariable('letter'), pos: jsPsych.timelineVariable('pos'), cond: jsPsych.timelineVariable('cond'), }, on_finish: function(data) { data.key = jsPsych.pluginAPI.convertKeyCodeToKeyCharacter(data.key_press) if (data.letter == 'L') { data.correct = Number(data.key == 'f') } else { data.correct = Number(data.key == 'j') } } } var trial_types = jsPsych.randomization.repeat(trial_types, 2); jsPsych.init({ timeline: [{ // [ と { 2つあります timeline_variables: trial_types, timeline: [trial], }], // } と ] 2つあります default_iti: 250, }); </script> </html>今回は実験データの追加・保存方法について紹介しました。気づいたらif文についても紹介していました。for文と合わせてjavascript(やその他多くのプログラミング言語)の基本構文になるので,ぜひ慣れ親しんでいただければと思います。
今回の記事では,例として,文字・位置・入力されたキーを追加でデータとして保存できるようにしましたが,分析で必要なのは,条件と正誤(とRT)なので,文字などを保存する必要はないかもしれません。なにはともあれ実際に実験が実施できそうなプログラムになってきました。あとは教示と参加者情報の取得になります。引き続き頑張りましょう。
function(){}の()内はdataでなくても,dとかhogehogeでも構いません。{}内の処理を実行するときに,()内で指定した名前で,試行変数のdataにアクセスできます。したがって,function(hoge){}とした場合はhoge.letterで各試行で提示された文字を参照できます。 ↩保存されたデータに表示された列名を使って,
data.列名とすれば,その列のデータにアクセスすることができます。例えば,data.rtとすれば,反応時間を取り出すことができます。data: {letter: ..., pos: ...}でデータを追加した場合は,data.letterなどでアクセスすることができ,それらはデータファイル内でletter,posという列に保存されるというところからも,data.列名でデータにアクセスすることができるということがわかると思います。 ↩
- 投稿日:2020-04-30T17:24:45+09:00
【第4回】timeline_variablesを駆使する
はじめに
本記事は,「jsPsychによる心理学実験作成チュートリアル」の第4回の記事です。第3回では刺激の提示位置を変更する方法を紹介しました。これまでの記事では
Lは左,Rは右にだけ提示されるようなコードを書いていました。サイモン課題では位置と反応の競合を生じさせるために,Lが右に提示されたり,Rが左に提示される試行も作成する必要があります。今回はそれら4条件の試行を見やすく,編集しやすいコードで作成する方法を紹介します。このチュートリアルシリーズの目的・概要等が気になった方はこちらの全体のまとめをご一読ください。
4条件分の
trial変数を作るまずは,復習を兼ねて,これまでの記事で説明した範囲で4条件分の試行が提示される実験を作成してみましょう。
コード例
no-timeline-variable.html<!DOCTYPE html> <html> <head> <script src="../jspsych.js"></script> <script src="../plugins/jspsych-html-keyboard-response.js"></script> <link rel="stylesheet" href="../css/jspsych.css"></link> <style> .text_left { position: absolute; left: 40%; top: 50%; transform: translateY(-50%) translateX(-50%); -webkit-transform: translateY(-50%) translateX(-50%); } .text_right { position: absolute; right: 40%; top: 50%; transform: translateY(-50%) translateX(50%); -webkit-transform: translateY(-50%) translateX(50%); } </style> </head> <body></body> <script> var trial_L_left = { type: 'html-keyboard-response', stimulus: '<div class="text_left">L</div>', choices: ['f', 'j'], trial_duration: 1000, } var trial_L_right = { type: 'html-keyboard-response', stimulus: '<div class="text_right">L</div>', choices: ['f', 'j'], trial_duration: 1000, } var trial_R_left = { type: 'html-keyboard-response', stimulus: '<div class="text_left">R</div>', choices: ['f', 'j'], trial_duration: 1000, } var trial_R_right = { type: 'html-keyboard-response', stimulus: '<div class="text_right">R</div>', choices: ['f', 'j'], trial_duration: 1000, } var trials = [trial_L_left, trial_L_right, trial_R_left, trial_R_right]; trials = jsPsych.randomization.repeat(trials, 2); jsPsych.init({ timeline: trials, default_iti: 250 }); </script> </html>基本的には,前回の記事で作成した
trial_L,trial_Rをコピペして,<div>タグのclassを適宜変更すれば,目的のプログラムは書けると思います。合わせて,変数の名前も提示される文字とその位置が一目でわかるような名前に変更しておくと良いと思います。これでサイモン課題は完成しました!あとはデータの保存です!
と言ってしまってもいいんですが,コピペをしながらコードを作成していて,こう思った方もいるのではないでしょうか。
各試行の
stimulus以外は全く同じなんだから,変数は一つだけ作成して,提示するstimulusだけを変更することはできないだろうか。これはもっともな指摘で,重要な視点です。というのも,上記のように4条件それぞれに対応する試行変数を作成した上で,もし反応として受け付けるキー(
choices)や1試行の持続時間(trial_duration)を変更することになったら,かなり手間ではないでしょうか。その修正をしている際にミスをしてしまうかもしれません。今回はサイモン課題ということで4種類の試行しかありませんが,提示する刺激の種類が100個もあった場合,それぞれの刺激を指定した試行変数を作るのは現実的ではありません。あるいは,ランダムな計算課題を提示するという場合,これまでの方法で実現するのは不可能でしょう。ということで,以降では,作成する試行変数を最少にして,必要な部分だけを各試行で変更する方法を紹介します。
jsPsych.timelineVariable()という関数を使うのですが,これを使う際に連想配列というものを利用するので,まずはそれについて説明します。連想配列
連想配列は,第2回で紹介した配列と同様に複数の要素を持つことのできるデータ構造ですが,単なる配列との違いは,保持している要素それぞれに,検索のためのキーをつけます。具体的には,
{キー1: 要素1, キー2: 要素2, ...}と書きます(配列の場合は[要素1, 要素2, ...])。そして,キーを指定することで,連想配列から要素を取り出すことができます。var dict = {letter: 'L', position: 'left'} // output // dict['letter'] -> 'L' // dict['position'] -> 'left' // dict.letter や dict.position で値を取り出すことも可能辞書を想像してもらえばわかりやすいかもしれません。辞書ではある言葉を引いてその意味を調べられるように,連想配列でもある言葉からそれに対応する要素を取り出す事ができます。というか,連想配列は英語ではdictionaryと呼ばれているようです(なので,上のコード例でも変数名を
dictとしてみました)。
jsPsych.timelineVariable()は事前に作成した連想配列に対して,jsPsych.timelineVariable('letter')とキーを指定して,対応する要素を取り出します。timelineVariables(その1)
それでは,連想配列と
jsPsych.timelineVariable()を用いて,たったひとつの試行変数だけで,4条件それぞれの試行を実施できるようにしましょう。まず,4条件分の連想配列を作成しましょう。今回のサイモン課題では,
stimulusの部分だけが試行ごとに変わるので,4条件それぞれのstimulusを作成して(コピペしてきて),以下のように,同じキーをつけて連想配列にし,それを並べた配列を作成します。var trial_types = [ {letter: '<div class="text_left">L</div>'}, {letter: '<div class="text_left">R</div>'}, {letter: '<div class="text_right">L</div>'}, {letter: '<div class="text_right">R</div>'}, ];わかりにくいかもしれませんが,この
trial_typesという変数は,連想配列を要素に持つ配列になります。=の直後に[,最後の行に]があります。配列の要素は,でさえ区切っていれば,縦に並べることができます。もっと配列の要素がシンプルな方がわかりやすいかもしれません。以下の例を見てください。例に出てくる2つの配列は,どちらも同じ配列です。var hairetsu1 = [1,2,3,4]; var hairetsu2 = [ 1, 2, 3, 4, ];そして,
stimulus: jsPsych.timelineVariable('letter')とした試行変数を一つだけ作成します。var trial = { type: 'html-keyboard-response', stimulus: jsPsych.timelineVariable('letter'), choices: ['f', 'j'], // 入力キーの指定 trial_duration: 1000, // 試行の持続時間 }最後に,
jsPsych.init()のtimelineを以下のように変更します。jsPsych.init({ timeline: [{ // [ と { 2つあります timeline_variables: trial_types, timeline: [trial], }], // } と ] 2つあります });こうすることによって,
stimulusで使用されているjsPsych.timelineVariables()がtrial_typesを参照してくれるようになります。実際に実験を走らせると,timeline_variablesに指定した連想配列の配列(trial_types)から,連想配列{letter: ...}が順番に取り出されます。そして,timelineで指定されている試行変数trialの中で,jsPsych.timelineVariables('letter')が用いられている部分に,キーletterと対応する連想配列の要素を適応します。3種類のカッコ(),[],{}が入り乱れているので,それぞれをどこで使っているのかを間違えないように注意してください。まとめると,以下のようなコードになります。
timeline_variables_1.html<!DOCTYPE html> <html> <head> <script src="../jspsych.js"></script> <script src="../plugins/jspsych-html-keyboard-response.js"></script> <link rel="stylesheet" href="../css/jspsych.css"></link> <style> .text_left { position: absolute; left: 40%; top: 50%; transform: translateY(-50%) translateX(-50%); -webkit-transform: translateY(-50%) translateX(-50%); } .text_right { position: absolute; right: 40%; top: 50%; transform: translateY(-50%) translateX(50%); -webkit-transform: translateY(-50%) translateX(50%); } </style> </head> <body></body> <script> var trial_types = [ {letter: '<div class="text_left">L</div>'}, {letter: '<div class="text_left">R</div>'}, {letter: '<div class="text_right">L</div>'}, {letter: '<div class="text_right">R</div>'}, ]; var trial = { type: 'html-keyboard-response', stimulus: jsPsych.timelineVariable('letter'), choices: ['f', 'j'], // 入力キーの指定 trial_duration: 1000, // 試行の持続時間 } // trial_typesをランダム化する trial_types = jsPsych.randomization.repeat(trial_types, 2); jsPsych.init({ timeline: [{ // [ と { 2つあります timeline_variables: trial_types, timeline: [trial], }], // } と ] 2つあります default_iti: 250, }); </script> </html>いかがでしょうか。
trial_L_left,trial_L_rightというように,各条件ごとの試行変数を作成するよりもコード全体がスッキリしたのではないでしょうか。また,試行変数を一つにまとめたことによって,もし,choicesやtrial_duration共通の項目に対する設定を変更する場合でも非常に容易に済ませることができそうです。注意しないといけないのは,timeline_variablesを設定すると,そこで指定されている配列
trial_typesに入っている要素の数が試行数になることです。ランダム化する場合は,trial_typesの方にrandomization.repeat()を適用するようにしてください。timelineVariables(その2)
さらにこのように思う人がいるかもしれません。
<div class="text_left">L</div>も,leftとL以外の部分はすべて共通なのだから,<div class="text_..."></div>も試行変数の中に入れてしまったほうがいいのではないか。この発想も
jsPsych.timelineVariable()で実装することができます。文字や位置をデータとして保存したい場合(第5回(リンク貼る))はこれから紹介する方法のほうが便利です。内容はすこし難しいかもしれませんが,理解してもらえればと思いますこのセクションの目標は,
<div class="text_位置">文字</div>の位置と文字が試行ごとに変わるようにすることです。ということはtrial_typesを以下のように変更すれば大丈夫です。var trial_types = [ {letter: 'L', pos: 'left'}, {letter: 'R', pos: 'left'}, {letter: 'L', pos: 'right'}, {letter: 'R', pos: 'right'}, ];次にstimulusの部分です。位置と文字はそれぞれ
jsPsych.timelineVariable('pos'),jsPsych.timelineVariable('letter')で呼び出せるので,それらを呼び出しつつ,<div class="text_位置">文字</div>となるように,以下のようにすればいい気がします。var trial = { type: 'html-keyboard-response', stimulus: '<div class="text_' + jsPsych.timelineVariable('pos') + '">' + jsPsych.timelineVariable('letter') + '</div>', choices: ['f', 'j'], // 入力キーの指定 trial_duration: 1000, // 試行の持続時間 }すでに
stimulusの部分が見にくくなってしまいましたね。stimulusの部分で使っている+は文字列を連結しています。timelineVariable()で呼び出した値がそれぞれleft,Lであれば,<div class="text_+left+">+L+</div>からすべてを連結して<div class="text_left">L</div>となります。これらをまとめると,と言いたいところなのですが,上記のような
stimulusの指定方法だと刺激はうまく提示されず,function() { return timeline.timelineVariable(varname); }というエラーメッセージが提示されます。なぜうまく回らないのかについては説明しません(できません)が,とにかく,エラーメッセージにあるようにfunction(){return ...}という形にする必要があるようです。今回の例であれば,var trial = { type: 'html-keyboard-response', stimulus: function(){ return '<div class="text_' + jsPsych.timelineVariable('pos', true) + '">' + jsPsych.timelineVariable('letter', true) + '</div>' }, choices: ['f', 'j'], // 入力キーの指定 trial_duration: 1000, // 試行の持続時間 }とします。突然
jsPsych.timelineVariable()にtrueが入ってきたと思いますが,function(){return ...}の形で使用する場合はこうする必要があるようです(理由は説明できません)。ということで,以上をまとめると以下のようなコードになります。
コード例
timeline_variables_2.html<!DOCTYPE html> <html> <head> <script src="../jspsych.js"></script> <script src="../plugins/jspsych-html-keyboard-response.js"></script> <link rel="stylesheet" href="../css/jspsych.css"></link> <style> .text_left { position: absolute; left: 40%; top: 50%; transform: translateY(-50%) translateX(-50%); -webkit-transform: translateY(-50%) translateX(-50%); } .text_right { position: absolute; right: 40%; top: 50%; transform: translateY(-50%) translateX(50%); -webkit-transform: translateY(-50%) translateX(50%); } </style> </head> <body></body> <script> var trial_types = [ {letter: 'L', pos: 'left'}, {letter: 'R', pos: 'left'}, {letter: 'L', pos: 'right'}, {letter: 'R', pos: 'right'}, ]; var trial = { type: 'html-keyboard-response', stimulus: function(){ return '<div class="text_' + jsPsych.timelineVariable('pos', true) + '">' + jsPsych.timelineVariable('letter', true) + '</div>' }, choices: ['f', 'j'], // 入力キーの指定 trial_duration: 1000, // 試行の持続時間 } var trial_types = jsPsych.randomization.repeat(trial_types, 2); jsPsych.init({ timeline: [{ // [ と { 2つあります timeline_variables: trial_types, timeline: [trial], }], // } と ] 2つあります default_iti: 250, }); </script> </html>その1と比べると,
trial_typesが見やすくなった代わりに,stimulusの部分が見にくくなってしまいました。どちらがいいかは好みと必要性によって変わってくるので,ご自身の実験内容に合わせて利用してください。なお,本チュートリアルでは各試行で提示された文字と位置もデータに保存します。その場合,その2の方法のほうが簡単なので,以降のチュートリアルで登場するコード例にはその2の方法を使います。念の為補足しておくと,
stimulus:以外にもjsPsych.timelineVariable()を利用することができます。例えば,以下のようにすれば,刺激によって提示時間を変えることができます。var trial_types = [ {letter: '<div class="text_left">L</div>', duration: 1000}, {letter: '<div class="text_left">R</div>', duration: 2000}, {letter: '<div class="text_right">L</div>', duration: 3000}, {letter: '<div class="text_right">R</div>', duration: 4000}, ]; var trial = { type: 'html-keyboard-response', stimulus: jsPsych.timelineVariable('letter'), choices: ['f', 'j'], trial_duration: jsPsych.timelineVariable('duration'), };おわりに
今回は
jsPsych.timelineVariable()を使って必要な部分だけを試行ごとに変更できるようにすることで,試行変数を減らし,コードの管理をしやすくする方法を紹介しました。後半になるにつれて話が複雑になってしまったような気もしますが,jsPsych.timelineVariable()がないとjsPsychで実験を作るのが難しくなってしまうと思うので,「その1」の部分だけでも理解してもらえればと思います。これでサイモン課題自体は完成しました。ただ,このままだと参加者が課題をどのように遂行したのかを知ることができません。そこで次回は,実験データを(手元のPC上に)保存する方法を紹介します。
- 投稿日:2020-04-30T17:17:52+09:00
【第3回】刺激の提示位置の調整 [jsPsych]
はじめに
本記事は,「jsPsych による心理学実験作成チュートリアル」の第 3 回の記事です。第 2 回では for 文を利用した刺激の系列提示の方法を紹介しました。今回は,刺激の提示位置の変更方法について解説します。詳細は右側の見出しリストをご覧ください。
このチュートリアルシリーズの目的・概要等が気になった方はこちらの全体のまとめをご一読ください。
刺激の提示位置を変更する
さて,さっそく素朴に刺激の提示位置を変更してみましょう。これまで提示する文字列を指定する際に
stimulus: 'L'stimulus: 'hello, world'のように,提示したい文字列を指定してきました。そして,このために,html-keyboard-responseというプラグインを使用してきました。実はこのプラグイン名にもあるように,stimulus:にhtml の構文を記述することができます1。htmlの構文で提示刺激の位置を調整します。以下のコードをコピペして実行してみてください。
change_position.html<!DOCTYPE html> <html> <head> <script src="../jspsych.js"></script> <script src="../plugins/jspsych-html-keyboard-response.js"></script> <link rel="stylesheet" href="../css/jspsych.css"></link> </head> <body></body> <script> var trial_L = { type: 'html-keyboard-response', stimulus: '<div style="position: absolute; left: 40%; top: 50%">L</div>', trial_duration: 1000, } var trial_R = { type: 'html-keyboard-response', stimulus: '<div style="position: absolute; right: 40%; top: 50%">R</div>', trial_duration: 1000, } var trials = [trial_L, trial_R] trials = jsPsych.randomization.repeat(trials, 2); jsPsych.init({ timeline: trials, }); </script> </html>いい感じに刺激を左右に提示することができたのではないでしょうか。この例では,
<div style="position: absolute; left: 40%; top: 50%">文字</div>というように,スタイルを記述した<div>タグというもので提示したい文字列を挟んで位置を調整します。styleで指定している項目はそれぞれ,
position: absolute: 絶対位置を用いる。ここでの絶対位置とは,ブラウザの上下左右の端を0とした位置のこと。left: 40%: 左から40%の位置(右端が100%)top: 50%: 上から50%の位置(下端が100%)となります。
left,top以外にも,right,bottomも使用することができます。上の例でも,Rを提示する際にはright: 40%と指定しています。また,位置の単位には%以外にもピクセルpxを用いることができます。基本的には%でいいでしょう。
positionについては他にもstatic,relative,fixedと指定することができます。詳しい説明は,google先生に任せることにしますが,文字列の位置を変更する場合に限ってはabsoluteを使えばいいでしょう。
<div>タグについてもgoogle先生を参照してください。styleを別で作っておく
位置の変更は
<div>タグを使いつつ,style=...で指定すればよいことがわかりました。しかし,位置を指定したせいでstimulus:の部分がかなり読みにくくなってしまいました。例えば,どこが実際に提示したい刺激なのかパッとわかりにくいです。位置の指定のために,styleの部分に様々な設定項目が並んでいるのが読みにくさ一因のような気がします。なんとか位置に関する設定を<div>タグの外側に書くことができれば,もう少し読みやすくなりそうです。HTMLでは,
<div style="...">で指定していたstyleを,<head>タグの部分で,名前をつけて用意しておくことができます。具体的には,以下のようにします。change_position.html<!DOCTYPE html> <html> <head> <script src="../jspsych.js"></script> <script src="../plugins/jspsych-html-keyboard-response.js"></script> <link rel="stylesheet" href="../css/jspsych.css"></link> <style> .text_left { position: absolute; left: 40%; top: 50%; } .text_right { position: absolute; right: 40%; top: 50%; } </style> </head> <body></body> <script> var trial_L = { type: 'html-keyboard-response', stimulus: '<div class="text_left">L</div>', trial_duration: 1000, } var trial_R = { type: 'html-keyboard-response', stimulus: '<div class="text_right">R</div>', trial_duration: 1000, } var trials = [trial_L, trial_R] trials = jsPsych.randomization.repeat(trials, 2); jsPsych.init({ timeline: trials, }); </script> </html>ポイントは2点です。
<head>の中に<style>というタグを新しく用意して,その中に<div style=...>で指定していた内容を書き写し,text_leftやtext_rightという名前をつけています。stimulusのもともと<div style="設定項目">だった部分を,<div class="スタイル名">として,その名前のstyleを読み込んでいます。2で述べたように,事前に設定したスタイルを
class=スタイル名で読み込むように変更することで,stimulusの部分がかなり見やすくなりました。また,<style>タグ内でスタイルを作成する際には,;で改行して,設定項目を縦に並べることができるため,何をどのように設定しているのかについても見やすくなりました。提示位置の確認
位置を変更することができましたが,私の拙いHTMLの知識ではこれで正しいの不安になるところです。もともと
stimulus:に文字列だけ指定していたときには,jsPsychのおかげで画面中央に刺激が提示されるようになっていました。もし,先程の例での位置の指定方法に問題がないのであれば,stimulus:に文字だけ指定したときと上下の位置に違いはないはずです。trial_L,trial_Rのどちらか一方のstimulus:を文字列だけの指定に戻して,上下の位置が一致しているかどうかを確認してみましょう。コード例ではRの提示位置の指定をなくしています。
コード例
check_position.html<!DOCTYPE html> <html> <head> <script src="../jspsych.js"></script> <script src="../plugins/jspsych-html-keyboard-response.js"></script> <link rel="stylesheet" href="../css/jspsych.css"></link> <style> .text_left { position: absolute; left: 40%; top: 50%; } .text_right { position: absolute; right: 40%; top: 50%; } </style> </head> <body></body> <script> var trial_L = { type: 'html-keyboard-response', stimulus: '<div class="text_left">L</div>', trial_duration: 1000, } var trial_R = { type: 'html-keyboard-response', stimulus: 'R', trial_duration: 1000, } var trials = [trial_L, trial_R] trials = jsPsych.randomization.repeat(trials, 2); jsPsych.init({ timeline: trials, }); </script> </html>実際に確認してみると,私の方法で位置を調整したLが明らかにRよりも低い位置に提示されていることがわかります。すべての刺激に対して同じ指定方法
top: 50%を用いれば,刺激間で上下の提示位置に差はないため,実験結果に影響はないと思われますが,jsPsychの「中央」からずれているのはすこし気持ちが悪いです。これを修正する方法を考えていきましょう。何の位置が調整されているのか
positionでの位置指定がどのよう行われているのかを理解するために,簡単な実験をしてみましょう。2つの刺激の内,一方の刺激は元の通りtop:50%で上から50%の位置に提示されるようにし,もう一方の刺激はbottom: 50%で下から50%の位置に提示されるようにしましょう。素朴にはどちらも同じ位置を指定しているように思われます。
コード例
change_position.html<!DOCTYPE html> <html> <head> <script src="../jspsych.js"></script> <script src="../plugins/jspsych-html-keyboard-response.js"></script> <link rel="stylesheet" href="../css/jspsych.css"></link> <style> .text_left { position: absolute; left: 40%; top: 50%; } .text_right { position: absolute; right: 40%; bottom: 50%; } </style> </head> <body></body> <script> var trial_L = { type: 'html-keyboard-response', stimulus: '<div class="text_left">L</div>', trial_duration: 1000, } var trial_R = { type: 'html-keyboard-response', stimulus: 'R', trial_duration: 1000, } var trials = [trial_L, trial_R] trials = jsPsych.randomization.repeat(trials, 2); jsPsych.init({ timeline: trials, }); </script> </html>しかし,試してみると,異なる位置に刺激が提示されたと思います。なぜこのようなことが起こるのでしょうか。実は,
topやbottomで提示刺激(HTMLの要素)のどの部分を上から・下から50%の位置に持ってきているのかが異なります。
topは提示刺激の上端が上から50%の位置に来るように調整します。bottomは提示刺激の下端が下から50%の位置に来るように調整します。フォントのサイズ分だけ刺激の上端と下端の位置は異なるため,結果的に刺激は異なった位置に提示されることになります。
この実験のおかげで,
topで指定しているのは刺激の上端の位置であること,そのためtop: 50%と指定しても,刺激は,ウィンドウの縦方向のちょうど真ん中には提示されないということがわかりました。なんとなく,これがjsPsychの標準の処理と違った結果になる原因な気がしてきました。そして,同じことが
left,rightで横方向の位置を指定したときにも当てはまります。leftは刺激の左端,rightは刺激の右端の位置を調整します。そのため,left: 50%と指定しても,刺激はウィンドウの横方向のちょうど真ん中には提示されませんし,left: 50%,right: 50%ではそれぞれ異なった位置に刺激が提示されます。この問題を避けて,
top: 50%・bottom: 50%(left: 50%・right: 50%)で刺激を縦(横)方向のちょうど真ん中に提示するには,刺激の中心が指定した位置に来るように調整すればよさそうです。刺激の中心の位置を調整する
指定した位置に刺激の中心を持ってくる方法を考えていきましょう。例えば,これまでのように
top: 50%,left: 40%とすると,刺激の左上端がブラウザの上端から50%, 左端から40%の位置に来ます。この位置に,刺激の中心を移動させたいのであれば,刺激の高さ(縦の長さ)の半分だけ上側に戻し,幅(横の長さ)の半分だけ左側に戻せばよいです。このページの「6. transform」にある図がわかりやすいです。これを実現するためには,styleに
transform: translateY(-50%) translateX(-50%);を追加します。translateYは縦方向,translateXは横方向に対応しており,正の値を指定すると下(右)方向に移動し,負の値だと上(左)方向に移動します。以下の例では,Lを画面左側に提示し,RはjsPsychの標準の処理で画面中央に提示するようにしています。実行して,LとRの縦方向の提示位置がどうなっているのかを確認してみてください。
change_position_transform.html<!DOCTYPE html> <html> <head> <script src="../jspsych.js"></script> <script src="../plugins/jspsych-html-keyboard-response.js"></script> <link rel="stylesheet" href="../css/jspsych.css"></link> <style> .text_left { position: absolute; left: 40%; top: 50%; transform: translateY(-50%) translateX(-50%); -webkit-transform: translateY(-50%) translateX(-50%); } .text_right { position: absolute; right: 40%; top: 50%; transform: translateY(-50%) translateX(50%); -webkit-transform: translateY(-50%) translateX(50%); } </style> </head> <body></body> <script> var trial_L = { type: 'html-keyboard-response', stimulus: '<div class="text_left">L</div>', trial_duration: 1000, } var trial_R = { type: 'html-keyboard-response', stimulus: 'R', trial_duration: 1000, } var trials = [trial_L, trial_R] trials = jsPsych.randomization.repeat(trials, 2); jsPsych.init({ timeline: trials, }); </script> </html>どうでしょうか。LとRが同じ高さに表示されていたのではないでしょうか。
text_leftのleftを50%に設定すると,LとRが全く同じ位置に提示されます。つまり,今回作成した位置の設定と,jsPsychが標準で処理してくれる位置の設定が同じであるということがわかります。ぜひ確認してみてください。上の例では使用していませんが,
text_rightを使うと,刺激の中心が画面の右側のちょうど40%の位置に提示されます。また,text_rightのtransformをtext_leftと見比べてみるとtranslateXの値の正負が異なっていますが,left:,right:で指定した位置に刺激の中心を持ってくるという点では,どちらもこれで問題ありません。それぞれのtranslateXの正負を反転させたりしていろいろ試してみてください。おわりに
今回は,提示位置の変更方法を紹介しました。ウェブブラウザを使用するjsPsychでは,位置を変更するためにHTMLタグを触る必要があります。これが曲者で,個人的にはかなり苦戦しました。この記事でみなさんが自在に位置調整できるようになったことを願うばかりです。
次回は,LとRをランダムに左右に提示する方法を紹介します。これまでの例ではLとRの提示位置は固定されていて,Lは左,Rは右にしか提示されていませんでした。次回の内容で,ひとまず,サイモン課題が完成します。引き続きがんばりましょう!
というよりも,単に文字列を入れただけのときでも html として処理されています。 ↩
- 投稿日:2020-04-30T17:10:48+09:00
【第2回】刺激の系列提示 [jsPsych]
はじめに
本記事は,「jsPsych による心理学実験作成チュートリアル」の第 2 回の記事です。第 1 回では jsPsych をインストールし,とりあえず刺激を提示することを体験しました。今回は,刺激の系列提示および for 文について解説します。詳細は右側の見出しリストをご覧ください。
このチュートリアルシリーズの目的・概要等が気になった方はこちらの全体のまとめをご一読ください。
サイモン課題とは
サイモン効果は,求められた反応と位置とが競合する場合に,競合しない場合と比べて反応が遅くなる効果です。例えば,左手のキーでの反応が求められた刺激が右側に提示されると,その刺激が左側に提示される場合と比べて反応が遅くなります。詳しくは文献等を参照してください。
本シリーズのサイモン課題では,「L」「R」という 2 つの文字を刺激として用いることにします。
刺激の素朴な系列提示
初めに,画面中央に L と R を 2 回ずつ提示してみましょう。以下にコードを示していますが,前回の復習も兼ねて,一度ご自身でコードを新しいファイルに書いてみてください。まったく空のファイルから始める必要はなくて,前回作成したコードをコピペしてもらって大丈夫です。
コード例
simple_sequence.html<!DOCTYPE html> <html> <head> <script src="../jspsych.js"></script> <script src="../plugins/jspsych-html-keyboard-response.js"></script> <link rel="stylesheet" href="../css/jspsych.css"></link> </head> <body></body> <script> var trial_L = { type: 'html-keyboard-response', stimulus: 'L', choices: ['f', 'j'], // 入力キーの指定 trial_duration: 1000, // 試行の持続時間 } var trial_R = { type: 'html-keyboard-response', stimulus: 'R', choices: ['f', 'j'], trial_duration: 1000, } jsPsych.init({ timeline: [trial_L, trial_R, trial_L, trial_R], }); </script> </html>さて,いかがでしょうか。実は前回説明していなかったのですが,
timeline:の[]の中に L を提示する試行(コード例ではtrial_L)と R を提示する試行(trial_R)を 2 回ずつ入れることで,L と R を 2 回ずつ提示することができます。ただ,このまま実際の試行数に合わせてコードを編集するのは不便です。例えば L と R を 100 回ずつ提示する場合,
[]の中にtrial_Lとtrial_Rをそれぞれ 100 個ずつコピペで入れるのは大変ですし,正確性にも欠けます。同じ処理を指定の回数だけ繰り返すのは,for文を使うことで簡単に実現することが可能です。for 文
javascript での for 文は以下のように書くことができます(動作しません)。
for (var i = 0; i < 繰り返したい回数; i++) { 繰り返したい処理; }
for文の直後にある()内には;区切りで 3 つの要素が入っています。以下のように,それぞれの要素でfor文の繰り返し回数に関わる設定がなされています。
iという名前のカウンタを作成し, 0 の状態にする(var i = 0)- カウンタの上限を指定(
i < 繰り返したい回数){}の処理が終わるたびにカウンタを 1 つすすめる(i++)
繰り返したい回数の部分に具体的な数字を入れることができます。jsPsych を使いながらfor文の挙動を確認してみましょう。以下のコードをコピペして保存したファイルを開いてみてください。add-for.html<!DOCTYPE html> <html> <head> <script src="../jspsych.js"></script> <script src="../plugins/jspsych-html-keyboard-response.js"></script> <link rel="stylesheet" href="../css/jspsych.css"></link> </head> <body></body> <script> var sum = 0 for (var i = 0; i < 10; i ++) { sum = sum + 2 // sum += 2 と書き換えられる } var show_sum = { type: 'html-keyboard-response', stimulus: String(sum), // 数字を文字列に変換 } jsPsych.init({ timeline: [show_sum], }); </script> </html>開いたブラウザに 20 と表示されたはずです。この例では,初期値を 0 に指定した
sumという名前の変数に,2 ずつ足すsum = sum + 2という処理を for 文で繰り返しています。10 回繰り返しているので,20 が表示されます。例えば,i < 30にすれば画面には 60 と表示されるはずです。配列
さて,
for文で処理を指定回数繰り返す方法を紹介しました。今回実際に繰り返したいのは「[]の中にtrial_Lとtrial_Rを入れる」という処理です。次にその処理を実行する方法を紹介します。
まず,[要素, 要素, ...]について簡単に説明します。これは,配列(array)と呼ばれるデータ型で,これまで見てきたように,[]の中にカンマ区切りで複数の要素を入れることができます。この中には,数字や文字列などあらゆる種類の要素を入れることができます。var array1 = [1, 2, 3, 4]; var array2 = ["a", "b", "c"];ここで注目してほしいのは,
trial_Lやtrial_Rのように,配列もvar 任意の名前 = [要素, 要素, ...]というように,名前を付けて利用できるということです。したがって,最初のサンプルコードのtimeline:に関わる部分を以下のように変更することができます。var trials = [trial_L, trial_R, trial_L, trial_R]; jsPsych.init({ timeline: trials, });そして,
.push(新しい要素)で,新しい要素を配列の最後に追加することができます。以下のコードのtrialsの最終的な中身は,直前のコードと同様に,[trial_L, trial_R, trial_L, trial_R]になります。var trials = []; trials.push(trial_L); trials.push(trial_R); trials.push(trial_L); trials.push(trial_R);この
.pushをforの中で繰り返し実行することで,trial_Lとtrial_Rを指定した回数だけ提示するプログラムを書くことができます。今回の最初に作成したコードを編集して,L と R が順番に 10 回ずつ提示されるようにしてみてください。
コード例
<!DOCTYPE html> <html> <head> <script src="../jspsych.js"></script> <script src="../plugins/jspsych-html-keyboard-response.js"></script> <link rel="stylesheet" href="../css/jspsych.css"></link> </head> <body></body> <script> var trial_L = { type: 'html-keyboard-response', stimulus: 'L', choices: ['f', 'j'], trial_duration: 1000, } var trial_R = { type: 'html-keyboard-response', stimulus: 'R', choices: ['f', 'j'], trial_duration: 1000, } var trials = []; for (var i = 0; i < 10; i++) { trials.push(trial_L); trials.push(trial_R); } jsPsych.init({ timeline: trials, }); </script> </html>刺激のランダム化
for文を使って,試行数を簡単に増やせるようになりました。しかし,現状のコードでは,L と R が規則的に提示されてしまっています。これでは参加者が次に提示される刺激を予測できてしまいます。これを回避するために,提示の順番をランダム化する必要があります。jsPsych には,
jsPsych.randomization.ほにゃららというランダム化に関する一連の関数が揃っています(公式リファレンス)。その一つである,jsPsych.randomization.repeatを使えば,任意の配列の中身をランダム化することができます。この関数は以下のように使います。var num_array = [1, 2, 3, 4, 5]; var num_array_rand = jsPsych.randomization.repeat(num_array, 1);シャッフルしたい配列を
()の最初に持ってこればいいだけです。それでは,先ほど for 文で作成した trial の配列trialsをランダム化してみてください。
コード例
<!DOCTYPE html> <html> <head> <script src="../jspsych.js"></script> <script src="../plugins/jspsych-html-keyboard-response.js"></script> <link rel="stylesheet" href="../css/jspsych.css"></link> </head> <body></body> <script> var trial_L = { type: 'html-keyboard-response', stimulus: 'L', choices: ['f', 'j'], trial_duration: 1000, } var trial_R = { type: 'html-keyboard-response', stimulus: 'R', choices: ['f', 'j'], trial_duration: 1000, } var trials = []; for (var i = 0; i < 10; i++) { trials.push(trial_L); trials.push(trial_R); } var trials_rand = jsPsych.randomization.repeat(trials, 1); jsPsych.init({ timeline: trials_rand, // 名前を変更し忘れない default_iti: 250, // 同じ文字が繰り返し提示されているのをわかりやすくするため }); // 以下のように元のtrialsを書き換えてしまってもこの場合は問題ない // trials = jsPsych.randomization.repeat(trials, 1); // jsPsych.init({ // timeline: trials, // default_iti: 250 // }); </script> </html>
jsPsych.randomization.repeatの()の 2 つ目にある数字については言及していませんでした。この数字は,関数の名前からも推察できるように,指定した配列の各要素を何度ランダムに取り出すのかを決定しています。例えば,2とすれば,以下のような配列を作ることができます(以下の例は公式リファレンスから転載)var myArray = [1, 2, 3, 4, 5]; var shuffledArray = jsPsych.randomization.repeat(myArray, 2); // output: shuffledArray = [1,3,4,2,2,4,5,1,5,3]つまり,この数字を変更することでランダム化された試行系列(配列)
trialsを手に入れることができます。本チュートリアルで完成を目指すサイモン課題では,本記事で紹介したfor文を使う必要は全く無かったということになります(以下のコード例を参照)。ただ,for 文を利用する機会はいずれあるので,覚えておいて損はないでしょう。
コード例
<!DOCTYPE html> <html> <head> <script src="../jspsych.js"></script> <script src="../plugins/jspsych-html-keyboard-response.js"></script> <link rel="stylesheet" href="../css/jspsych.css"></link> </head> <body></body> <script> var trial_L = { type: 'html-keyboard-response', stimulus: 'L', choices: ['f', 'j'], trial_duration: 1000, } var trial_R = { type: 'html-keyboard-response', stimulus: 'R', choices: ['f', 'j'], trial_duration: 1000, } var trials = [trial_L, trial_R] trials = jsPsych.randomization.repeat(trials, 10); jsPsych.init({ timeline: trials, default_iti: 250, // 同じ文字が繰り返し提示されているのをわかりやすくするため }); </script> </html>おわりに
今回は刺激をランダムに系列提示する方法を紹介しました。そのために for 文と
jsPsych.randomization.repeatを利用しました。しかしながら,刺激は画面中央にしか提示されていません。サイモン課題の一致条件・不一致条件を作り出すために,次回は刺激の提示位置を変更する方法を紹介します。
- 投稿日:2020-04-30T17:06:45+09:00
【第1回】jsPsychのインストール,刺激の単純な提示
はじめに
本記事は,「jsPsych による心理学実験作成チュートリアル」の第1回の記事です。今回はjsPsychをインストールし,とりあえず刺激を提示してみます。詳細は右側の見出しリストをご覧ください。
このチュートリアルシリーズの目的・概要等が気になった方はこちらの全体のまとめをご一読ください。
下準備
jsPsych のインストール
公式のダウンロードページにある zip ファイルをダウンロードしてください。特別な必要のない限り,最新版で大丈夫です。少しスクロールすると下図のような表示があるはずです。
ダウンロードしたら zip ファイルを展開してください。
練習用のフォルダを作成
展開したフォルダ内に
practiceみたいな名前で練習用のフォルダを新規作成してください(なんでもいいです)。ファイルの拡張子を表示する
https://pc-karuma.net/windows-10-show-explorer-file-name-extension/
を参考にファイルの拡張子が表示されるようにしてください。リンク先のページでは拡張子についても簡単に説明があります。テキストエディタを用意する
実験用のスクリプトを編集するためにテキストエディタが必要です。なんでも構いません。私は普段Visual Studio Codeを使用しています。表示言語を日本語にしたい場合はこのページを参考にしてみてください。Windows 用の説明ですが,Mac でも同様の方法で日本語化できるはずです。
とりあえず jsPsych を使ってみる
新規ファイルの作成
テキストエディタを開いて,新しいファイルを新規作成してください。Visual Studio Code なら(おそらく他の多くのエディタでも)
control + n(Mac の場合はcommand + n)で新規ファイルが作成できます。文字列を提示する
新しいファイルに以下のコードを入力し,ファイルを保存してください。初回はファイル名を要求されるので後で見返したときに分かりやすい名前にしましょう。例えば,下のコードエリアの左上に書いてあるように,
hello-world.htmlと付けて,ダウンロードした jsPsych のフォルダ下に作成したpracticeフォルダの中に保存してください。ファイル名は半角英数字にし,最後に.html をかならず付けてくださいそして,フォルダ内に保存された
hello-world.htmlをダブルクリックで開いてください。ブラウザ(chrome や firefox など)が起動し,中央に「hello world!」と書かれたページが開くはずです。hello-world.html<!DOCTYPE html> <html> <head> <script src="../jspsych.js"></script> <script src="../plugins/jspsych-html-keyboard-response.js"></script> <link rel="stylesheet" href="../css/jspsych.css"></link> </head> <body></body> <script> var trial = { type: 'html-keyboard-response', // 試行のタイプ指定 stimulus: 'hello world!', // 提示する刺激 } jsPsych.init({ timeline: [trial], // 提示順序の指定 }); </script> </html>さっそくコードがたくさん書かれていて気が滅入りそうですが,ひとまず重要なのは以下の 2 点です。
- jsPsych プラグインの読み込み(4-5 行目)
- メインのコード(10-18 行目)
まず,プラグイン(必要な機能がまとめられたファイル)の読み込みについて,ここでは 2 つのプラグインを読み込んでいます1。
1 つ目は,jspsych.jsという jsPsych の本体です。これがすべての機能の核となっているので,どのコードでもこれを必ず最初に読み込みます。
2 つ目は,jspsych-html-keyboard-response.jsという(主に)文字列の提示2およびそれに対する反応の取得を可能にするプラグインです。次に,メインのコードを解説します。
var trial = ...の部分で「ある試行」を作成しています。今回はその試行にtrialという名前をつけています。{}の中で試行の設定ができます。
type: 'html-keyboard-response'と最初に試行のタイプを指定し,jspsych-html-keyboard-response.jsを使った試行(つまり,文字列が刺激で,場合によっては反応を取得する試行)であることを宣言しています。stimulus: 'hello world!で,提示する文字列を指定しています。試行を作成する際には,初めの
varと{},各行の最後にある,を忘れないでください。
jsPsych.init()で実験のフローを作成し,実行します。timeline:で,試行の順序を指定できます。今回はtrialという試行を 1 度だけ実行するので,[trial]とします([]を忘れない!)。それぞれの行の右のほうに書いているコメントの通りです。なお,Javascript では,
//に続けてメモを書くことができます3。他の人や将来の自分のために,なるべくメモを残しておくようにしましょう。日本語を提示する
先程のコードの
stimulus:の部分を日本語(例えば,「こんにちは,世界!」)に変えてみてプログラムを実行してみましょう。人によっては文字化けしてうまく表示されないと思います。これを回避するためには,以下のように,<head>というタグの直後に,<meta charset="utf-8">を追加しましょう。実験の刺激や教示で日本語を使用する場合は,必ずつけるようにしましょう。これをつけない場合,文字化けするかどうかは,使用するPC・ウェブブラウザの設定によって変わってきます。オンライン実験を実施する場合には参加者によってPC・ブラウザの設定がそれぞれに異なるはずなので,参加者によっては刺激が適切に提示されず実験を遂行できなくなってしまいます。hello-world-jp.html<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <!-- これを足す --> <script src="../jspsych.js"></script> <script src="../plugins/jspsych-html-keyboard-response.js"></script> <link rel="stylesheet" href="../css/jspsych.css"></link> </head> ... </html>ただ,本チュートリアルでは当分の間日本語を提示することはないので,サンプルのコードに
<meta ...>の部分は記載されていません。キー入力を指定する
先ほどの例では,どのキーを押しても提示された
hello worldが画面から消失するようにできていました。気づいていなかった場合は試してみてください。jsPsych では,受け付けるキーを指定しない場合,すべてのキー入力を受け付けるようになっています。受け付けるキーを指定する方法はとても簡単です。刺激作成のコードに,
choices: [何かしらのキー]を足します。accept-some-keys.html<!DOCTYPE html> <html> <head> <script src="../jspsych.js"></script> <script src="../plugins/jspsych-html-keyboard-response.js"></script> <link rel="stylesheet" href="../css/jspsych.css"></link> </head> <body></body> <script> var trial = { type: 'html-keyboard-response', stimulus: 'hello world!', choices: ['f', 'j'], // 入力キーの指定 } jsPsych.init({ timeline: [trial], }); </script> </html>この例では,
'f'と'j'を指定しています(このとき文字の前後にクォーテーション'を必ずつける)。一部のキー(e.g., 方向キー)を指定する場合は,対応するキーコードを[]内に入れる必要があります。例えば,右方向キーの入力を受け付けたい場合は39をいれます。キーコードで指定する場合はクォーテーションは付けてはいけません。ちなみに,スペースキーは'space'で反応するみたいです。各キーに対応するキーコードは https://www.cambiaresearch.com/articles/15/javascript-char-codes-key-codes で調べることができます。また,[]内にひとつでも認識されないキー(コード)が入っていると,キー入力を認識しなくなります。その他,刺激の提示時間(
stimulus_duration),1 試行の持続時間(trial_duration),反応と同時に次の試行に遷移するかどうか(response_ends_trial)を指定することができます(公式リファレンス)。例えば,以下のようにtrialの中身を変更すると,2000ms の試行のうち,はじめの 1000ms だけ刺激が提示されることになります。反応があっても 2000ms 経過するまで参加者は待つことになります。色々試してみてください。var trial = { type: "html-keyboard-response", stimulus: "hello world!", choices: ["f", "j"], stimulus_duration: 1000, trial_duration: 2000, response_ends_trial: false, // 反応取得後に次の試行に移る場合は true にする };画像を提示する
下準備
提示するための画像を練習用のフォルダ(
practice)に用意します。リンクから画像ファイルをダウンロードしてjspsych-logo.jpgという名前で保存してください。コードを書く
新しいファイルを作成して,以下のコードを書いてください。
show-image.html<!DOCTYPE html> <html> <head> <script src="../jspsych.js"></script> <script src="../plugins/jspsych-image-keyboard-response.js"></script> <link rel="stylesheet" href="../css/jspsych.css"></link> </head> <body></body> <script> var trial = { type: 'image-keyboard-response', stimulus: 'jspsych-logo.jpg', } jsPsych.init({ timeline: [trial] }); </script> </html>ブラウザの中央に画像が表示されたはずです。
直前の
hello-worldから 3 点変更点があります。
- 読み込むプラグインを
jspsych-image-keyboard-response.jsに変更(さっきは jspsych-html-keyboard-response.js)。type:をimage-keyboard-responseに変更(さっきは html-keyboard-response)stimulus:に保存したファイル名を指定うまく画像が表示されない場合は,ファイルの名前や保存先が間違っているかもしれません。
おわりに
以上で第 1 回は終了です。文字・画像刺激以外にも,動画や音声,リッカートスケールなども提示することができます。jsPsych 内にある examples フォルダに,サンプルのコード(それぞれ jspsych-video-keyboard-response.html, jspsych-audio-keyboard-response.html, jspsych-survey-likert.html など)をブラウザで開いて試してみたり,テキストエディタで開いてそのスクリプトを確認してみてください。
次回は,実験課題の基本である刺激の系列提示を行います。
公式リファレンスを見ると,jsPsych の開発者らは,
jspsych.jsをライブラリと呼んでいますが,これらの用語の違いを私は理解していないので,簡単のため,どちらもプラグインと呼びます。 ↩「文字列の提示」ではなく「html の要素の提示」と表現するほうが正しいです。このプラグインを使って画像を提示することも可能です。画像の提示にはそれ専用のプラグインがあるので,この
html-keyboard-responseは文字列提示用のプラグインだと考えてもらって(今のところは)大丈夫だと思います。ただ,提示する文字列の見栄えや位置を変更する際には,stimulus:の部分で html タグを追加したりすることになるので,このプラグインが単に文字列を扱っているのではなく,html を扱っているのだということを頭の片隅に置いておいてもらえればと思います。 ↩もう少し詳しく言うと,
//より右側はコードとして認識・処理されません。したがって,メモを書くときだけでなく,一時的にある行のコードを実行したくない場合にも//は便利です。行頭に//をつけて行ごとコメント化し,処理を回避できます(コメントアウトと言います)。 VS Code 上では,control + /(Mac ならCommand + /)で(複数行でも)コメントアウトができます。 ↩
- 投稿日:2020-04-30T15:42:13+09:00
Vue.jsについて 簡易まとめメモ
Vue.jsとは
JavaScriptフレームワーク
MVVMパターン(Model-View-ViewModel)
ユーザーインターフェイスを構築するためのプログレッシブフレームワークMVVMパターンとは
- Model:データ管理 ViewModelの担当外全てを担当
- View:ユーザインターフェース
- ViewModel:Model→Viewへの出力データを渡し、View→Modelへの入力データを渡す
プログレッシブフレームワークとは
どんなとき、どんな規模にも段階的に柔軟に使える思想
・コンポーネント設計・ライブラリの活用・小規模から大規模まで可能になるコンポーネント設計とは
コンポーネント(component) 部品、成分、構成要素などの意味を持つ
GUIのパーツをモジュール化したもの
簡単に言うと部品を作成して組み立てようという事アトミックデザイン(Atomic Design)とは
- atoms:原子
- ボタン、テキスト、ボックスなど
- Molecule:分子
- Atomsの組み合わせ ログインフォームなど
- Organisms:有機体
- MoleculesやAtomを組み合わせ ヘッダーなど
- Template:テンプレート
- Organismsを組み合わせ ワイヤーフレーム
- Pages:ページ
- 文言、画像、データなどの最終調整
ボタンなどの小さなコンポーネント(atoms)を作り
ログインフォームなどの中くらいのコンポーネント(Molecule)を作り
ヘッダーやページ全体(Organisms、Template、Pages)を作っていく考え方
- 投稿日:2020-04-30T12:53:06+09:00
Node - ES6 imports cannot find module
Problem
index.jsimport HttpConnect from './src/Library/Connect/HttpConnect'; ... ... ...node --experimental-modules index.js$ node --experimental-modules index.js internal/modules/run_main.js:54 internalBinding('errors').triggerUncaughtException( ^ Error [ERR_MODULE_NOT_FOUND]: Cannot find module '\home\johnny\test\src\Library\Connect\HttpConnect' imported from \home\johnny\test\index.js at finalizeResolution (internal/modules/esm/resolve.js:272:11) at moduleResolve (internal/modules/esm/resolve.js:652:10) at Loader.defaultResolve [as _resolve] (internal/modules/esm/resolve.js:695:13) at Loader.resolve (internal/modules/esm/loader.js:97:40) at Loader.getModuleJob (internal/modules/esm/loader.js:243:28) at ModuleWrap.<anonymous> (internal/modules/esm/module_job.js:42:40) at link (internal/modules/esm/module_job.js:41:36) { code: 'ERR_MODULE_NOT_FOUND'Solution
index.jsimport HttpConnect from './src/Library/Connect/HttpConnect.js'; <- including the file extension ... ... ...https://stackoverflow.com/questions/60059121/nodejs-es6-imports-cannot-find-module
- 投稿日:2020-04-30T12:01:52+09:00
HTML5のcanvas要素を使って図形を描く
始まる前に
今回はHTML5のcanvasを使って簡単な図形を描いてみたいと思います。
描画する図形
ソースコード
index.html<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script type="text/javascript"> var ctx; function init() { var canvas = document.getElementById("canvas"); ctx = canvas.getContext("2d"); //コンテキストの設定 ctx.strokeStyle = "#FF0000"; ctx.fillStyle = "#00FF00"; ctx.lineWidth = 10; ctx.lineCap = "round"; ctx.shadowColor = "#000000"; ctx.shadowBlur = 20; //線を引く ctx.beginPath(); ctx.moveTo(100, 100); ctx.lineTo(180, 250); ctx.stroke(); //矩形を塗りつぶす ctx.fillRect(300, 100, 100, 150); //矩形を描く ctx.strokeRect(500, 100, 100, 150); } </script> </head> <body onload="init()"> <canvas id="canvas" width="700" height="400"></canvas> </body> </html>描画の手順
- HTMLでcanvas要素を定義
- JavaScriptでcanvas要素を取得
- canvas要素からコンテキストを取得
- コンテキストに色や線の太さなどを設定
- コンテキストに対して線や矩形などの描画を行う
ソースの説明
HTMLではcanvas要素を定義します。
<canvas id="canvas" width="700" height="400"></canvas>ここで注意してほしいのは幅をwidth属性で,高さをheight属性で指定することです。
この指定が正しくなされてないと意図した大きさで描画されないこともありますので注意が必要です。
JavaScriptでは8列の
var canvas = document.getElementById("canvas");でcanvasへの参照を取得し、次に9列の
ctx = canvas.getContext("2d");でコンテキストを取得します。このctxが絵筆などの情報を格納するオブジェクトとなります。
多くのプログラムでは、コンテキストをctxやcontextといった広域変数に格納するのが主流です。
引数に「2d」とありますが、現在のHTML5では「2d」しか指定出来ません。
コンテキストを取得したらプロパティ(絵筆の属性)を設定します。(列11-17)
主なプロパティは以下の通りです。
プロパティ 内容 ctx.strokeStyle 線や輪郭の色 ctx.fillStyle 塗りつぶしの色 ctx.lineWidth 線の幅 ctx.lineCap 線の終端の形状で、butt、round、squreの値が使用される ctx.shadowColor 現状の影の色 ctx.shadowBlur 影に適用するぼかす範囲 コンテキストにプロパティを設定した後、線、矩形の塗りつぶし、矩形を描画しています。
描画する際に筆を動かすようにcanvasでは必ず「パス」と呼ばれる軌跡を設定します。
そのパスを初期化するのが「beginPath()」です。
「moveTo()」で筆を下ろし、「lineTo()」で筆を動かします。
その後、「stroke()」で初めて線が描画されます。
ちなみに、「fill()」では「パス」で囲まれた範囲が塗りつぶされます。
「fillRect()」と「strokeRect()」は名前から予想できると思いますが、塗りつぶした矩形の描画をしてくれます。
座標系はマウスイベントと同じく、canvasの左上を原点(0,0)、右方向にx軸、下方向にy軸となります。
引用 : Dripcoke
- 投稿日:2020-04-30T11:31:31+09:00
`SyntaxError: Cannot use import statement outside a module` JSでimportの箇所でエラー起きる
エラー
(node:211) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension. ~/getMessageOnSlack.js:1 import axios from 'axios'; ^^^^^^ SyntaxError: Cannot use import statement outside a module解決法
エラーが言っているように、package.jsonに
type:moduleを追加するだけpackage.json{ "name": "", . . . "type": "module", }
- 投稿日:2020-04-30T10:44:46+09:00
Node.jsを使ってHTTPサーバを作ってみる
はじめに
この記事はなんとなくJSとかを勉強している学生がメモ代わりに書いているものです。内容は期待しないでください。
1.仮想環境を構築する
今回はUbuntuで行うのでiTerm2で仮想環境を起動する。
起動したところで
1.Virtual Box(バーチャルボックス)
2.Vagrant(ベイグラント)
という2つのソフトウェアを使った仮想環境でUbuntuを使用します。
cd ~/vagrant/ubuntu
vagrant up
vagrant sshUbuntuがインストールされたディレクトリに移動。vagrant upは仮想的なPCにインストールされたUbuntuを起動するコマンドで,vagrant sshはVagrantの仮想マシンがセットされている状態でSSHに接続します。
2.プログラムのひな形(テンプレート)を用意する
ディレクトリ内に以下を記述します。
yarn init
echo "'use strict';" > ファイル名
1行目はyarnで新しいプロジェクトを始める際に記述するものです。
3.プログラムを書く
先ほど記述したファイルに以下を記述します。
'use strict';
const http = require('http');
const server = http.createServer((request, response) => {
response.writeHead(200, {
'Content-Type': 'text/plain; charset=utf-8'
});
response.write(request.headers['なんでもいい']);
response.end();
});
server.listen(8000, () => {
console.log('Listening on 8000' );
});
1行目はJSをstrictモードで利用するための記述です。
2行目はhttpモジュールを引数httpに代入しています。
3~9行目はサーバに関する記述です。
引数に代入したhttpモジュールを使ってサーバを構築しています。記述方法は以下参照。
http.createServer( サーバー側の処理 )
今回はサーバ側の処理でアロー関数を使用しており,引数の一つ目にはリクエストが二つ目にはレスポンスが代入されています。
res.writeHead(200, {
'Content-Type': 'text/plain; charset=utf-8'
});
このコードは200という成功を示すステータスコードと共に、サーバが扱う情報の設定をレスポンスヘッダを書き込んでいます。
7行目はwrite関数を使用してリクエストのヘッダーに文字列を表示しています。表示したい内容によっては以下のように書くこともできます。
res.write(
'<!DOCTYPE html><html lang="ja"><body><h1>文字列</h1></body></html>'
);
8行目はサーバの書き出しが終わったことを示しています。10行目以降はサーバ起動するポートを8000としてlisten関数で特定のポートからリクエストがないかを永続的に調べています。今回はリクエストがあり次第コンソールで文字列を表示しています。
4.サーバを起動してみる
以下を記述してREPLで動作を確認します。
node ファイル名
先ほどのコンソールの文字列が表示されたら成功です。お疲れ様でした!!yarnとは
yarnはnodeをインストールすると自動でインストールされるnpmと役割は同様のパッケージマネージャー(https://yarnpkg.com/en/)。
並行処理でのインストールによってnpmよりも高速にパッケージをインストールできる。httpモジュールとは
「httpモジュール」はHTTPサーバーやHTTPクライアントとしての機能を構築するために使われます。自分のwebサイトをネット上に公開したり、フォームなどからデータを送受信できます。もちろん、静的なWebサイトだけでなくTwitterのような大きなWebサービスを構築することも可能になります。
ポートとは(参照:https://www.nic.ad.jp/ja/basics/terms/port-number.html)
TCP/IP通信においては,IPアドレスがあればネットワーク上のコンピュータを一意に識別することができますが,該当コンピュータのどのプログラムに通信パケットを届けるかは,IPアドレスだけでは決定できません。どのプログラムに通信パケットを渡すのかを決定するために,ポート番号を使用します。

























