読者です 読者をやめる 読者になる 読者になる

銀の弾丸

プログラミングに関して、いろいろ書き残していければと思っております。

SVGの重なり順序を操作する「svg-z-order」

JavaScriptで、SVG要素の重なり順(Z-Order)を操作するモジュール svg-z-order をnpmで公開したのでご紹介。

SVGの図形の要素はドキュメント内での定義順に、奥から手前へ向けて描画されますが、 これを変更するには要素を並べ替えるしかありません。 HTML/CSSのz-indexスタイルのような方法が(今のところ?)無いのです。

並べ替えはSVGに限らず、DOMのNodeクラスinsertBeforeメソッドを使うのですが、数値指定の z-index ほど、お手軽ではありませんので、この(↓)モジュールを作りました。

f:id:takamints:20170423172653p:plain

使い方

特定要素を最前面へ持ってくるコードを、ちょっと冗長に書いてみた。

//モジュール取得
var svgz = require("svg-z-order");// (1)

//SVG要素 g#foo を最前面へ表示
var g = svg.getElementById("foo");
var svgzG = svgz.element(g); //(2)
svgzG.toTop(); //(3)
 
// D3.jsで使う(4)
var d3 = require("d3");
var d3g = d3.select("#foo");
svgz.element(d3g.node()).toTop();
  1. モジュールをインポートして、
  2. element メソッドで、DOM要素を参照するインスタンスを作り
  3. 最前面へ表示します。
  4. D3.jsを使っていますが、例として書いているだけで、依存関係はありません。

上の例では、Browserifyを使って、 var svgz = require("svg-z-orger");でモジュールを取り込んでいますが、 HTMLで<script src="svg-z-order.js"/>としている場合は、 (2)の行の svgz.element(g) を、 svgz_element(g) に変更して下さい。 requireできない環境ではグローバルスコープに定義します。

API

element(e:Element)

SVG要素を参照する SVGZElement クラスのインスタンスを返します。 Z-Orderを操作するAPIは SVGZElementクラスのメソッドです。

パラメータ

  • e:Element - 参照するDOM要素を指定します。

SVGZElementクラス

SVGZElement.toTop()

参照している要素を最前面に移動。

SVGZElement.toBottom()

参照している要素を最背面に移動。

SVGZElement.moveUp(e:Element / n:number)

参照している要素を上(前面)へ移動。

パラメータ

  • e:Element - この要素よりも上になるように移動します。
  • n:number - 要素の並びの中でこの回数分だけ上へ移動します。

要素を指定したときはその要素よりも上へ。数値指定した場合は、その回数だけ上へ移動。

SVGZElement.moveDown(e:Element / n:number)

参照している要素を下(背面)へ移動。

パラメータ

  • e:Element - この要素よりも下になるように移動します。
  • n:number - 要素の並びの中でこの回数分だけ下へ移動します。

要素を指定したときはその要素よりも下へ。数値指定の場合は、その回数だけ下へ移動。

サンプル

ドロップダウンでメソッドを選んで、円をクリックするとメソッドを適用します。

コードはこちら:/sample/web/index.js

リポジトリ

www.npmjs.com

github.com

いくつになってもお勉強

JavaScriptのプログラムからSVG要素を最前面へもってくる方法を検索したら、結構微妙な情報が出てきました。

曰く、

  1. SVG単体では不可能なので、HTMLのDIVにSVGを書いて重ね合わせて、DIVのz-indexで制御するしかありません。
  2. 対象のSVG要素をディープコピーして appendChild 。元要素は removeChild。

こういう情報を見て、当初「なるほどヤヤコシイなー」と思ったのですが、どうにも納得がいきませんで、いろいろやってみているとDOMの標準機能で可能と判明。

いくつになってもお勉強です。

微妙な説明を信じちゃうのは、Node.appendChildNode.insertBeforeは、既にDOMツリーにある要素を移動してくれるということをしっかり認識できていないということでしょう。自分も今まで「追加、挿入」ってイメージしか持っていませんでしたので。

D3.js v4 でドラッグするには d3.drag() で behavior を取得する

D3.jsでドラッグイベントを処理する必要があったのですよ。

ほぼ初めてのD3ですからグーグル先生にいろいろ聞いて、「ほうほうなるほど」と学習していたのですけど、 ドラッグに関して各所で示されていたサンプル通りにやってみたら、まさかのエラー。

結局は、大きな問題ではありませんで、ひと言で言ってしまえばバージョン違いだったのですが、どうにも日本語の情報が少ないようなので書いておきます。

f:id:takamints:20170418212231p:plain

英語版です。v4に関して日本語の書籍が見当たらないです。

D3.js/v3でのコード

「D3.js ドラッグ」などと検索して出てくるサンプルコードの多くが、D3.jsのv3(バージョン3)のために書かれたコードで、最新のv4では動かないんですね。

v3でドラッグするには、d3.behavior.drag()で、Drag Behavior を取得するのですが、v4ではこれが動かない。

// D3.js v3でDrag Behaviorを取得

var drag = d3.behavior.drag(); //V4ではエラー

D3.js/v4でのOKコード

じゃあどうやるんだって―と、v4以降では以下のように、d3.drag()とするのだそうだ。

// D3.js/V4 でDrag Behaviorを取得

var drag = d3.drag(); // これでOK

リンク

リポジトリCHANGES にはしっかり書いてありました。

CHANGES
github.com

Dragに関する詳細は以下に
github.com

いやしかし

メジャーバージョンが上がっているので、APIに互換性がなくなってても文句は言わない約束だけど、 レガシーコードには警告を出すとかって対応があればモアベターね。

JavaScriptのラムダ式(アロー関数)は丸括弧で括らなければ即時実行できませんのね

Node.jsで以下のようにラムダ式を即時実行していたのですが、ブラウザでは構文エラーとなって動かないんです。

(()=>{
    console.log("これ動きません");
}());

まさかコレが動かないとか思いもよらず。 どう見直しても問題があるとは思えなかったのだが動かないから仕方がない。

追記(2017-03-29)JavaScriptの言葉的には「ラムダ式」ではなく「アロー関数」のようですので、タイトルにのみ追記しました。

f:id:takamints:20170328172435p:plain
Link: Flickr PAGE - CC BY 2.0

色々やってみた結果、ブラウザで動作させるには、以下のようにラムダ式全体を丸括弧で括る必要があるようですね。 しかし、なんだかカッコが多すぎ。ラムダ的に台無し感がありますなー。

((()=>{
    console.log("これでヨシ。だがしかし・・・");
})());

丸括弧なしでは関数オブジェクトとして評価されていないようです。 アロー演算子=>)の優先度の問題かな?

そもそも、どちらが正しいのかわからなかったので、調べてみると、どうやらECMAScript6の仕様のバグだそうで、 Node.jsでは便利な様に解釈して実装されているのかも知れませんが、今の所ブラウザではどうにもならないようですね。

d.hatena.ne.jp

即時実行するときはラムダ式を使わないというのが安全・安心? 関数内のthisが定義時にバインドされちゃうようなので、単なる無名関数の構文糖として使うのはマズいらしいし・・・

JavaScriptでマイクロ秒単位の定期処理を実行する(npm fractional-timer)

JavaScriptで1ミリ秒より短いインターバルタイマー処理を提供するモジュールのご紹介。

実際のところ、精度はよくありませんので、クリティカルな用途には向きません。 単純な処理をなるべく高速にタイマーで実行したいけど、標準タイマーの1ミリ秒では遅すぎる・・・といった時に使える感じ。 処理が重い場合は、呼び出し間隔は長くなります。

Chrome と Node.Js で、正しく動いていることを確認していますが、 FirefoxやEdgeでは、まともに動いていなかったので、ご注意下さい。

f:id:takamints:20170327172258p:plain
Link: Flickr PAGE - CC BY-SA 2.0

使い方

使い方は遅延時間に実数を指定できる点を除けば、標準のsetIntervalclearIntervalと同じです。 遅延時間は標準と同じくミリ秒単位で指定します(0.1=100マイクロ秒)。

以下のように呼び出すことで、タイマーの数と遅延時間等を調整して、定期処理を実行します。

(function() {
    "use strict";
    let ft = require("fractional-timer");
    let ftid = ft.setInterval(
        function(){
            ft.clearInterval(ftid);
        },
        0.001);// 1 microseconds
}());

インストール

npmでインストールすればNode.jsから使えます。

> npm install fraction-timer

ブラウザのWEBアプリでは、browserifyrequire するか、 scriptタグで読み込んで、FractionalTimerクラスの静的メソッドsetInterval/clearIntervalを直接使ってくださいね。

リポジトリ

www.npmjs.com

github.com

その他、能書き

やっていることは単純です。複数のインターバルタイマーで一つの処理を呼び出しているだけです。

例えば、1マイクロ秒で実行したい場合、1ミリ秒のタイマーを1000個使って呼び出すということです。

実際には、指定された間隔から算出される単位時間あたりの実行回数を計算し、タイマーの数と間隔を自動的に算出、ベストエフォートで呼び出します。

MZ-700フルJavaScriptエミュレータ」で、エミュレーションをワーカースレッドの定期処理で実装していて、当初は単一のインターバルタイマーで、最も高速になるように調整していたのですが、ある時タイマーが二重に動いてしまったことがあって、速度がなんと2倍程度に跳ね上がっていたのです。

「うわあ!世紀の大発見!!!」と思ったけれど、単一スレッド内ではシーケンシャル動作が保証されているので、当然の挙動なんですよね。

ただ、こういうことをやってる人があまりいないところを見ると、需要はないみたいなんですけどね(笑)

.NETのアプリケーション設定を編集可能なXMLに保存する

ここではアプリケーションの設定を編集可能なXMLファイルとして利用するための基本クラスとサンプルをご紹介。

.NETのプロジェクトには「アプリケーション構成ファイル」があって、設定情報(構成情報?)を書いておけるような感じですが、 インストーラーでセットアップすると、読み取り専用のアプリケーションフォルダに入ってしまい、編集するには管理者権限が必要になります。 つまり運用中の書き換えは考慮されていないってことですね。

f:id:takamints:20170221171853p:plain

基礎からわかる C#
基礎からわかる C#
posted with amazlet at 17.02.21
シーアンドアール研究所 (2017-02-16)
売り上げランキング: 16,889

■■■ 目次 ■■■

  1. はじめに - 機能仕様
  2. XML設定クラス定義例:SampleSettingsクラス
  3. 設定ファイルの読み込み処理
  4. XMLファイルの例:SampleSettings.xml
  5. XML設定ファイルの基本クラス:XmlSettingFileクラス
  6. 設定ファイルはWindowsの特殊フォルダ以下へ
  7. 参考サイト

1. はじめに - 機能仕様

ここでやりたいことをザックリ以下に箇条書き。

  1. アプリ起動時に特定のフォルダに保存されたXMLの設定ファイルを読み込む。
  2. ファイルがない場合は、アプリ起動時に既定の内容で設定ファイルを作成する。
  3. 設定情報はクラスのインスタンスとしてプログラムからアクセスできる。
  4. 最低限プリミティブ型が正しく読み書きできること。

設定読み込み時の値のチェックはしていません。 バリデーション機能を持たせたメタクラスを導入すれば可能ですが、大げさになり過ぎるので、またおいおい。

2. XML設定クラス定義例:SampleSettingsクラス

以下がXML設定情報を保持するクラス。

設定ファイルに保存したい値をメンバとして定義したシンプルなクラスです。 XMLシリアライズを利用しているのでパブリックなデフォルトコンストラクタが必要です。 基本クラスの XmlSettingFileクラス は、下で説明します。

/// <summary>
/// XMLアプリケーション設定サンプル
/// </summary>
public class SampleSettings : Utils.XmlSettingFile
{
    /// <summary>
    /// 既定の設定情報を生成。
    /// (デフォルトコンストラクタは必ず必要)
    /// </summary>
    public SampleSettings() { }

    //以下のメンバーはXMLに保存される

    public string Name { get; set; } = "Koji Takami";
    public string Email { get; set; } = "vzg03566@gmail.com";
    public int FeatureOption { get; set; } = 0;
    public bool DebugOption { get; set; } = false;

    //保存したくないメンバーは以下のように宣言
    [System.Xml.Serialization.XmlIgnore]
    public string NotSaved;
}

3. 設定ファイルの読み込み処理

上のクラスのインスタンスXMLファイルから読み込むには、XmlSettingFile.Loadを使用します。 このメソッドには、保存場所のルートを決めるスペシャルフォルダの列挙子と、既定値を保持する設定情報のインスタンスを指定します。

以下のコードでは、設定ファイルを読み込んで、SampleSettingクラスのインスタンスを返します。

public partial class App : Application
{
    static public SampleSettings sampleSettings = new SampleSettings();
    public App()
    {
        try {
            sampleSettings = Utils.XmlSettingFile.Load(
                Environment.SpecialFolder.CommonApplicationData,
                new SampleSettings()) as SampleSettings;
        } catch (Exception ex) {
            Console.WriteLine("Error:{0}", ex.Message);
        } finally {
            sampleSettings.Save();
        }
    }
}
  • 上のコードで読み書きされる設定ファイルは、「C:\ProgramData\(CompanyName)\(ProductName)\(ProductVersion)\SampleSetting.xml」です。ここで、(CompanyName)(ProductName)(ProductVersion)アセンブリのファイルバージョン情報の設定値です。プロジェクトの初期状態ではCompanyNameが空白ですが、その部分抜きで正しいパスに生成されます。
  • 読み込み時にファイルがなければ、既定値のインスタンスを使用して作成されます。
  • ここでは、アプリケーションクラスのコンストラクタで読み出していますが、別のタイミングでも問題はありません。
  • 読み込んだ直後に保存しているのは、開発中に追加項目を反映したりObsoleteな設定項目をXMLから削除するためです。これがなくてもファイルがなければ作成されます。このSaveは、必ずしも必要ではありません。

4. XMLファイルの例:SampleSettings.xml

最初の実行時に生成されたファイルの中身は以下のようになります。 書き換えて読み込んだら、ちゃんと設定値が反映されます。それが目的ですからね。

<?xml version="1.0" encoding="utf-8"?>
<SampleSettings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Name>Takami Koji</Name>
  <Email>vzg03566@gmail.com</Email>
  <FeatureOption>0</FeatureOption>
  <DebugOption>false</DebugOption>
</SampleSettings>

5. XML設定ファイルの基本クラス:XmlSettingFileクラス

以下が、このエントリーの御本尊。 派生クラスのメンバーをXMLファイルへシリアライズ/デシリアライズするクラス。 (名前空間は適当に変えてください)

ファイル名の決定や、アクセス権の設定などで長くなっていますが、やってることは意外にシンプル。XmlSerializerのお陰様。

using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Text;
using System.Xml.Serialization;

namespace StudyDotNet.Utils
{
    public class XmlSettingFile
    {
        /// <summary>
        /// 設定ファイルを読み込む
        /// </summary>
        /// <param name="folderId">
        ///     スペシャルフォルダを指定する</param>
        /// <param name="initial">
        ///     ファイルがない場合の既定値を持つ
        ///     インスタンス</param>
        /// <param name="filename">
        ///     ファイル名。
        ///     省略時はクラス名を採用する</param>
        /// <returns></returns>
        public static XmlSettingFile Load(
            Environment.SpecialFolder folderId,
            XmlSettingFile initial,
            string filename = null)
        {
            //ファイル名を生成
            string pathname = CreatePathname(
                folderId, initial, filename);

            //設定ファイルがなければ既定の値で生成する
            if (!File.Exists(pathname)) {
                Save(pathname, initial);
                AllowUsersReadAndWrite(pathname);
            }

            //設定ファイルを読み込む
            return Read(pathname, initial.GetType());
        }

        /// <summary>
        /// 設定ファイル名。
        /// 実際に読み込んだもの。
        /// 書き戻す場合にも利用する。
        /// </summary>
        [XmlIgnore] public string Filename { get; private set; }

        /// <summary>
        /// 設定ファイルへ書き戻す。
        /// プログラムから設定値を変更した場合などに使用。
        /// </summary>
        public void Save() { Save(Filename, this); }

        #region StaticPrivate

        /// <summary>
        /// 与えられた情報からファイル名を生成する
        /// </summary>
        /// <param name="folderId"></param>
        /// <param name="initial"></param>
        /// <param name="filename"></param>
        /// <returns></returns>
        static private string CreatePathname(
            Environment.SpecialFolder folderId,
            XmlSettingFile initial,
            string filename)
        {
            //ファイル名が指定されていないならクラス名
            if(filename == null) {
                filename = initial.GetType().Name;
            }

            //拡張子がXMLでない場合はXMLに変更する
            if(Path.GetExtension(filename) == "") {
                filename = Path.ChangeExtension(filename, "xml");
            }

            //ファイル名を決定
            string pathname = Path.Combine(
                CreateFolder(folderId), filename);

            return pathname;
        }

        /// <summary>
        /// スペシャルフォルダ以下のパスを生成
        /// </summary>
        /// <param name="folderId"></param>
        /// <returns></returns>
        static private string CreateFolder(
            Environment.SpecialFolder folderId)
        {
            //アセンブリのファイルバージョン情報を利用してフォルダを決定
            var verinfo = FileVersionInfo.GetVersionInfo(
                Assembly.GetExecutingAssembly().Location);

            // 空白の項目はパスに含まれない。
            // 例えば CompanyName が空の場合、
            // @"C:\ProgramData\ProductName\1.0.0.0" などとなる
            string path = Path.Combine(new string[] {
                Environment.GetFolderPath(folderId),
                verinfo.CompanyName,
                verinfo.ProductName,
                verinfo.ProductVersion,
            });

            //フォルダがないなら生成する
            if(!Directory.Exists(path)) {
                Directory.CreateDirectory(path);
            }

            return path;
        }

        /// <summary>
        /// 設定を設定ファイルに保存
        /// </summary>
        /// <param name="filename"></param>
        /// <param name="setting"></param>
        static private void Save(
            string filename, XmlSettingFile setting)
        {
            var writer = new StreamWriter(
                filename, false, new UTF8Encoding(false));
            var xml = new XmlSerializer(setting.GetType());
            xml.Serialize(writer, setting);
            writer.Close();
        }

        /// <summary>
        /// 設定ファイルを読み込む
        /// </summary>
        /// <param name="filename">設定ファイル</param>
        /// <param name="type">設定データの型</param>
        /// <returns>type型のインスタンス。
        /// ファイルない場合はnullを返す。</returns>
        static private XmlSettingFile Read(
            string filename, Type type)
        {
            var reader = new StreamReader(
                filename, new UTF8Encoding(false));
            var xml = new XmlSerializer(type);
            var setting = xml.Deserialize(reader) as XmlSettingFile;
            reader.Close();
            if(setting != null)
            {
                setting.Filename = filename;
            }
            return setting;
        }

        /// <summary>
        /// 指定ファイルにUsersグループのユーザーの読み書き権限を与える
        /// </summary>
        /// <param name="filename"></param>
        static private void AllowUsersReadAndWrite(string filename) {
            FileSystemAccessRule rule = new FileSystemAccessRule(
                new NTAccount("Users"),
                FileSystemRights.Write | FileSystemRights.Read,
                AccessControlType.Allow);
            FileSecurity security = File.GetAccessControl(filename);
            security.AddAccessRule(rule);
            File.SetAccessControl(filename, security);
        }

        #endregion

    }
}

6. 設定ファイルはWindowsの特殊フォルダ以下へ

参考のため、アプリケーションの設定ファイルを保存するために適当なスペシャルフォルダを抜き出してみました。 下表はDOBON.NET プログラミング道:特殊ディレクトリのパスを取得するからの抜粋です。

Windows / .NET的に設定ファイルは、以下のようなフォルダ以下に、アプリケーションの企業名、製品名、バージョンなどのフォルダを掘って、保存するのが望ましいようです。

Environment.SpecialFolder列挙体のメンバ 具体例(参考)
1 CommonApplicationData C:\ProgramData
2 ApplicationData C:\Users\(UserName)\AppData\Roaming
3 LocalApplicationData C:\Users\(UserName)\AppData\Local
4 UserProfile C:\Users\(UserName)
6 MyDocuments C:\Users\(UserName)\Documents
5 CommonDocuments C:\Users\Public\Documents

全ユーザーに共通する設定なら1が良いと思います。ユーザー別なら2か3、4もあり。ただしローミングが必要なら2であるべきですね。

5と6については非推奨。Documentsに設定ファイルを置くのはちょっと違うかもしれないですからね。

7. 参考サイト