2013年4月28日日曜日

Strokesというストロークを再生出来るお絵描きWebアプリ的なのを作りました

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

はじめに


 2日ほど前の夜中に@kojira@hamatzらが

といった感じで語らっていたので、

 という事で作りました。ものはこちらです。HTML5的なWebアプリです。

Strokes


できる事


 大体以下の事が出来ます。メインはトレースモードでしょうか。書いた絵を1ストロークずつ再生し、それをなぞる感じです。 
  • 線画を書く
  • アンドゥ・リドゥ
  • 絵を保存する(localStorage)
  • 保存した絵の再編集
  • 保存した絵の削除
  • 書いた絵のストロークを再生
  • 絵のトレースモード


  • デモ


    詳しくは以下の動画を。絵描きさんの絵をトレースモードでなぞって練習とか色々捗るんじゃないかなと。



    おわりに


     まだ排他処理やってなかったりだとか、サーバへのデータ保存出来なかったりだとか、ペイントツール部分もしょぼかったりします。なんか要望とかアイデアあればStrokes Issuesに書いて下さい。あわよくばpull request下さい。あとUIデザイン周りと、コンテンツとして良さげな線画書いてくれる方募集。

    2013年4月5日金曜日

    tweets.zipをMongoDBに突っ込んでNoSQLを学ぶ(導入編)

    このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
     インスパイアされたので。MongoDBで同じような事やってみよーーーーかなーーーーーーーーーと。まぁあんまりMongoDB知らないですけど。
    tweets.zipをMySQLに突っ込んでSQLを学ぶ(導入編)

    なにはともあれアーカイブをダウンロード


    これはtweets.zipをMySQLに突っ込んでSQLを学ぶ(導入編)に書かれているのでそちらを参考にして下さい。サーセン


    tweets.csvを探す


    実はtweets/data/js/tweets配下に月ごとのつぶやきのJSONデータがあるのでそれ使ったらええんちゃう、と思ったんですけど、ファイル分かれてるしめんどーなんでtweets.csvでいいです。tweets.csvはtweetsの直下にあります。



    MongoDBでcsvをインポートする


    csvのインポートはMongoDBに付属している"mongoimport"コマンドを利用します。が、残念ながらTwitterから落とせるcsvは色々腐っていてすんなりMongoDBにインポートする事が出来ません。という事でまずcsvデータを加工します。

    csvを加工する


    csv加工もjavascriptを使いたいなーと思うんでPhantomJSを使います。
    PhantomJS: Headless WebKit with JavaScript API

    Macならportsとかでスカッと入れられるんで入れたらええんちゃうんかと。brewは知らんけどあるでしょう。
    sudo port install phantomjs
    

    んで、以下のscriptを適当に保存してphantomjsで実行してcsvを加工します。ちょい長いです。具体的には"(ダブルクォーテーション)とかがフリーダムなんでちゃんとパースして適切なエスケープを入れてます。僕の4年分の15000件分程度なんでまだパターン漏れがあるかもしれません。改行も\nでやってるんでWindows環境だとダメかもしれません。死んだら教えて下さい。お悔やみ申し上げます。
    var fs = require("fs");
    var stream = fs.open(phantom.args[0], "r");
    var tweets = stream.read();
    stream.close();
    
    var STATUS = {
     "WAIT":"0",
     "PARSE":"1",
     "CONTENT":"2"
    }
    var eol = "\n";
    var size = tweets.length;
    var i=0;
    var result = "";
    var status = STATUS.WAIT;
    while(true){
     if(i >= size){
      break;
     }
     var c = tweets[i];
     if(status === STATUS.WAIT){
      if(c === "\""){
       status = STATUS.PARSE;
       result += c;
      }
     }
     else if(status === STATUS.PARSE){
      if(c === "\""){
       status = STATUS.CONTENT;
      }
      else if(c === eol){
       result += "\\n";
      }
      else if(c === ","){
       result += "\\,";
      }
      else{
       result += c;
      }
     }
     else if(status ===  STATUS.CONTENT){
      if(c === "," || c === eol){
       result += "\""+c;
       status = STATUS.WAIT;
      }
      else{
       result += "\\\"";
       if(c === "\""){
        result += "\\\"";
       }
       else{
        result += c;
       }
       status = STATUS.PARSE;
      }
     }
     i++;
    }
    console.log(result);
    phantom.exit();
    

    上記スクリプトを"organizing_csv.js"として保存したとします。phantomjsでcsvを加工します。
    phantomjs organizing_csv.js tweets.csv  > tweets.csv.opt
    


    csvをインポートする


    さて、準備が出来たのでMongoDBに突っ込みます。ここはもう簡単です。mongoimportに各種オプションを指定して読ませるだけです。-dはDB名、-cはコレクション名です。折角なんでtwitterとtweetsとしました。

    mongoimport -d twitter -c tweets -type csv -file tweets.csv.opt  -headerline
    
    >connected to: 127.0.0.1
    >Thu Apr  4 22:49:14   3258534/3382985 96%
    >Thu Apr  4 22:49:14    14700 4900/second
    >Thu Apr  4 22:49:14 imported 15205 objects
    

    無事15205件のツイートがMongoDBに突っ込まれました。


    MongoDBで黒歴史を遡る


    折角インポートしたんでちょっと触ってみましょうか。

    mongoに接続してDB一覧を見る


    show dbsでDB一覧が見れます。twitterデータベースが出来ている事が確認出来ます。
    mongo
    >MongoDB shell version: 2.2.1
    >connecting to: test
    > show dbs
    > local (empty)
    > twitter 0.203125GB
    

    DBを選択する


    んで、まずDBを選択します。
    use twitter
    

    次にコレクションを見てみましょう。tweetsがありますねー。
    show collections
    
    >system.indexes
    >tweets
    

    とりあえず全件表示、件数表示


    MongoDBはjavascriptで操作します。まーどうでもいいです。全件出す場合はこんな感じ。
    db.tweets.find();
    
    { "_id" : ObjectId("515e68ee81dc484aaf93e666"), "tweet_id" : NumberLong("319741609916919808"), "in_reply_to_status_id" : "", "in_reply_to_user_id" : "", "retweeted_status_id" : "", "retweeted_status_user_id" : "", "timestamp" : "2013-04-04 09:21:54 +0000", "source" : "Tweet Button", "field11" : "ほお [ A3直前!NFCLAB追い込みハッカソン(開発者枠) http://t.co/VJL7MP2hhj ]", "field12" : "http://connpass.com/event/2094/" }
    { "_id" : ObjectId("515e68ee81dc484aaf93e667"), "tweet_id" : NumberLong("319702386367135744"), "in_reply_to_status_id" : "", "in_reply_to_user_id" : "", "retweeted_status_id" : "", "retweeted_status_user_id" : "", "timestamp" : "2013-04-04 06:46:03 +0000", "source" : "Tweet Button", "field11" : "実機でFirefox OSアプリ試せるのねぇ [ Hello Firefox OS: Firefox MarketplaceをAndroid版Firefox Auroraで試す http://t.co/1v5u27ZHlj ]", "field12" : "http://bsfirefox.blogspot.com/2013/04/firefox-marketplaceandroidfirefox-aurora.html" }
    { "_id" : ObjectId("515e68ee81dc484aaf93e668"), "tweet_id" : NumberLong("319614807709990912"), "in_reply_to_status_id" : "", "in_reply_to_user_id" : "", "retweeted_status_id" : "", "retweeted_status_user_id" : "", "timestamp" : "2013-04-04 00:58:02 +0000", "source" : "TweetDeck", "field11" : "今出しょう子も可愛くしたげてよ・・・" }
    { "_id" : ObjectId("515e68ee81dc484aaf93e669"), "tweet_id" : NumberLong("319612544287399937"), "in_reply_to_status_id" : "", "in_reply_to_user_id" : "", "retweeted_status_id" : "", "retweeted_status_user_id" : "", "timestamp" : "2013-04-04 00:49:03 +0000", "source" : "TweetDeck", "field11" : "staticおじさんとか可愛いもんやで" }
    { "_id" : ObjectId("515e68ee81dc484aaf93e66a"), "tweet_id" : NumberLong("319610859334811651"), "in_reply_to_status_id" : "", "in_reply_to_user_id" : "", "retweeted_status_id" : "", "retweeted_status_user_id" : "", "timestamp" : "2013-04-04 00:42:21 +0000", "source" : "Tweet Button", "field11" : "20年くらい前の話かと思った。ひっくり返った。 [ Windows 8のC++でプログラミングの常識がひっくり返った http://t.co/7bagFJXZIs ]", "field12" : "http://itpro.nikkeibp.co.jp/article/Watcher/20130331/467401/" }
    { "_id" : ObjectId("515e68ee81dc484aaf93e66b"), "tweet_id" : NumberLong("319601772056420352"), "in_reply_to_status_id" : "", "in_reply_to_user_id" : "", "retweeted_status_id" : "", "retweeted_status_user_id" : "", "timestamp" : "2013-04-04 00:06:15 +0000", "source" : "TweetDeck", "field11" : "そろそろGoogle pluserになるかぁー?" }
    ...
    

    件数はどうかな。
    db.tweets.find().length();
    
    >15205
    

    年月日でソート


    じゃーちょっとソートを。古い順になりましたねー
    db.tweets.find().sort({"timestamp":"desc"});
    
    { "_id" : ObjectId("515e68f081dc484aaf9421ca"), "tweet_id" : NumberLong("5473927171"), "in_reply_to_status_id" : "", "in_reply_to_user_id" : "", "retweeted_status_id" : "", "retweeted_status_user_id" : "", "timestamp" : "2009-11-06 08:16:59 +0000", "source" : "web", "text" : "仕事だ" }
    { "_id" : ObjectId("515e68f081dc484aaf9421c9"), "tweet_id" : NumberLong("5511509893"), "in_reply_to_status_id" : "", "in_reply_to_user_id" : "", "retweeted_status_id" : "", "retweeted_status_user_id" : "", "timestamp" : "2009-11-07 18:12:52 +0000", "source" : "web", "text" : "眠い" }
    { "_id" : ObjectId("515e68f081dc484aaf9421c8"), "tweet_id" : NumberLong("5511523599"), "in_reply_to_status_id" : "", "in_reply_to_user_id" : "", "retweeted_status_id" : "", "retweeted_status_user_id" : "", "timestamp" : "2009-11-07 18:13:33 +0000", "source" : "web", "text" : "Androidの青空文庫ビューアいい加減にリリースしないといけないがバージョン互換の確認とDBの再定義が面倒臭すぎて死にそう。" }
    { "_id" : ObjectId("515e68f081dc484aaf9421c7"), "tweet_id" : NumberLong("5552112211"), "in_reply_to_status_id" : "", "in_reply_to_user_id" : "", "retweeted_status_id" : "", "retweeted_status_user_id" : "", "timestamp" : "2009-11-09 05:40:16 +0000", "source" : "web", "text" : "なんだこりゃ" }
    { "_id" : ObjectId("515e68f081dc484aaf9421c6"), "tweet_id" : NumberLong("5552376671"), "in_reply_to_status_id" : "", "in_reply_to_user_id" : "", "retweeted_status_id" : "", "retweeted_status_user_id" : "", "timestamp" : "2009-11-09 05:57:45 +0000", "source" : "web", "text" : "とりあえずフォローしまくればいいんだな。きっと" }
    ...
    

    キーワードで抽出


    じゃーキーワードで。findの所に検索する正規表現をぶっこめば抽出出来る。
    db.tweets.find({"text":/java/})
    
    { "_id" : ObjectId("515e68ee81dc484aaf93e873"), "tweet_id" : NumberLong("312226191383871488"), "in_reply_to_status_id" : "", "in_reply_to_user_id" : "", "retweeted_status_id" : "", "retweeted_status_user_id" : "", "timestamp" : "2013-03-14 15:38:19 +0000", "source" : "web", "text" : "interfaceのデフォルト実装がコンパイル通らなかった。なんで。java8ェ・・・" }
    { "_id" : ObjectId("515e68ee81dc484aaf93e8c4"), "tweet_id" : NumberLong("311691694364041216"), "in_reply_to_status_id" : "", "in_reply_to_user_id" : "", "retweeted_status_id" : "", "retweeted_status_user_id" : "", "timestamp" : "2013-03-13 04:14:25 +0000", "source" : "web", "text" : "RTをふぁぼはあったが遂にjava8追加情報は無かった・・・。" }
    { "_id" : ObjectId("515e68ee81dc484aaf93e8c9"), "tweet_id" : NumberLong("311632352789004288"), "in_reply_to_status_id" : "", "in_reply_to_user_id" : "", "retweeted_status_id" : "", "retweeted_status_user_id" : "", "timestamp" : "2013-03-13 00:18:37 +0000", "source" : "web", "text" : "Java8とJava7の新機能使ったコード集書いてみた。Java8ってもっといっぱいある気がするけど誰かおせーて [ java8_java7.java https://t.co/Tzlc7BtuRR ]", "expanded_urls" : "https://gist.github.com/sys1yagi/5141091" }
    { "_id" : ObjectId("515e68ee81dc484aaf93e8f8"), "tweet_id" : NumberLong("311334806694793217"), "in_reply_to_status_id" : "", "in_reply_to_user_id" : "", "retweeted_status_id" : "", "retweeted_status_user_id" : "", "timestamp" : "2013-03-12 04:36:16 +0000", "source" : "web", "text" : "そういやjava7辺りでjavaコントロールパネルで出来たっぽい気がしてきた。Macなら環境設定のその他の\\", "expanded_urls" : "java\\", "field9" : "な" }
    { "_id" : ObjectId("515e68ee81dc484aaf93e8fa"), "tweet_id" : NumberLong("311333756243943425"), "in_reply_to_status_id" : "", "in_reply_to_user_id" : "", "retweeted_status_id" : "", "retweeted_status_user_id" : "", "timestamp" : "2013-03-12 04:32:06 +0000", "source" : "web", "text" : "target=jsr14でjava8をAndroidで使うよ( ー`дー´)キリッ" }
    { "_id" : ObjectId("515e68ee81dc484aaf93eaec"), "tweet_id" : NumberLong("306254125945982977"), "in_reply_to_status_id" : "", "in_reply_to_user_id" : "", "retweeted_status_id" : "", "retweeted_status_user_id" : "", "timestamp" : "2013-02-26 04:07:28 +0000", "source" : "web", "text" : "Rubyのcaseってjavaのswitchだよね(^q^)" }
    ...
    

    ふむふむじゃぁあのキーワードで。。。



    やめろ!Twitter始めたてでノリが分からなくて入学したての大学生みたいな寒い感じのツイートをしていた過去を掘り返すのはやめろ!


    まぁ自分だけのアレなんでええんちゃいますか。MongoDBに突っ込めばキーワードで抽出したりソートとか簡単にできますねー。まぁMySQLもそうですけど。色々遊べそうですよね。一個考えたのは年月日指定するとそのタイムラインを再現する奴ですね。その日時のつぶやきがリアルタイムで更新されていくと。一人だとアレですけどユーザ多かったら「あーこういう感じのTLだったなぁーほげー」となるかもしれませんね。でもデータでかいし管理めんどいんでやりません。