SVGの重なり順序をJavaScriptで制御する「svg-z-order」
JavaScriptからSVG要素の重なり順(Z-Order)を操作するモジュールのご紹介。
SVG要素には、HTMLに使えるz-index
スタイルが効きません。
なので、重なり順を変更するには、単純に要素を並べ替える必要があります。
コードからDOM要素を並べ替えるにはNodeクラスのinsertBefore
メソッドが使えます。
Node
クラスはSVG要素SVGElement
の基本クラスですから、SVG要素でも使えます。
SVG関連最新記事:
takamints.hatenablog.jp
takamints.hatenablog.jp
ところで、SVG要素にはz-index
が効かないことを「SVGの致命的な欠陥」と言ってる人がいますが、さすがにそれは言い過ぎですよ。SVGは単体で存在できる画像ファイルであってHTMLとの直接的なつながりはありません。単体のSVGファイル中で図形の重なり順序をz-index
で定義されていると、画像としてどのように表示されるか理解しにくいはずです。要素の順序で重なり順序が決定される方が、SVG的に「よりシンプル」なんですね。
さらに「HTMLのDIVにSVGを入れてz-index
で…」などというヤヤコシイ方法が検索で出てくるのですが、これは既にSVGではなくなっていますから、一般的な解決法とはいえないように思います。全体のスケールを変えるとかViewportを移動したいとかいったシーンで困りそう。
というか、DOM要素の並べ替え自体は前述のようにinsertBeforeで簡単なんです。 DOMのベタな扱いに慣れていないと難しそうに思うのかもしれないですね。
ただし、ベタにやると「どれの前に持ってくる?」といったコードが煩雑になるのは確かです。 てことで怒りに任せて(?)npmを書きなぐりました。以降、このモジュールのサンプルとかAPIを説明します。
とりあえずは動くサンプル
以下は、このモジュールを使ったサンプルです。コードは下の方に掲載してます。 3つの円をクリックすると、その要素にドロップダウンで選ばれているメソッドを適用します。
使い方
このモジュールを使って、特定要素を最前面へ持ってくるコードを少し冗長に書いてみました。
//モジュール取得 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();
- モジュールをインポートして、
element
メソッドで、DOM要素を参照するインスタンスを作り- 最前面へ表示します。
- 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
リポジトリ
いくつになってもお勉強
JavaScriptのプログラムからSVG要素を最前面へもってくる方法を検索したら、結構微妙な情報が出てきたので少しインターネッツに失望しました。
曰く、
こういう情報を見て、当初「エラいヤヤコシイな」と思いましたが、どうにも納得がいかなくて、いろいろやってみているとDOMの標準機能で可能だと判明。
当初「複製作って、追加して、元のを消す」という処理手順をイメージしていましたが、移動できますね。
Node.appendChild
やNode.insertBefore
は、既にDOMツリーにある要素は移動するのですが、メソッド名を見る限りではそう思えないところが落とし穴かも。
いくつになってもお勉強です。
Node.js / npm 関連記事
takamints.hatenablog.jp takamints.hatenablog.jp takamints.hatenablog.jp takamints.hatenablog.jp takamints.hatenablog.jp
D3.js v4 でドラッグするには d3.drag() で behavior を取得する
D3.jsでドラッグイベントを処理する必要があったのですよ。
ほぼ初めてのD3ですからグーグル先生にいろいろ聞いて、「ほうほうなるほど」と学習していたのですけど、 ドラッグに関して各所で示されていたサンプル通りにやってみたら、まさかのエラー。
結局は、大きな問題ではありませんで、ひと言で言ってしまえばバージョン違いだったのですが、どうにも日本語の情報が少ないようなので書いておきます。
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の言葉的には「ラムダ式」ではなく「アロー関数」のようですので、タイトルにのみ追記しました。
色々やってみた結果、ブラウザで動作させるには、以下のようにラムダ式全体を丸括弧で括る必要があるようですね。 しかし、なんだかカッコが多すぎ。ラムダ的に台無し感がありますなー。
((()=>{ console.log("これでヨシ。だがしかし・・・"); })());
丸括弧なしでは関数オブジェクトとして評価されていないようです。
アロー演算子(=>
)の優先度の問題かな?
そもそも、どちらが正しいのかわからなかったので、調べてみると、どうやらECMAScript6の仕様のバグだそうで、 Node.jsでは便利な様に解釈して実装されているのかも知れませんが、今の所ブラウザではどうにもならないようですね。
即時実行するときはラムダ式を使わないというのが安全・安心? 関数内のthisが定義時にバインドされちゃうようなので、単なる無名関数の構文糖として使うのはマズいらしいし・・・
JavaScriptでマイクロ秒単位の定期処理を実行する(npm fractional-timer)
JavaScriptで1ミリ秒より短いインターバルタイマー処理を提供するモジュール fractional-timer
のご紹介。
Link: Flickr PAGE - CC BY-SA 2.0
実際のところ、精度はよくありませんので、クリティカルな用途には向きません。 単純な処理をなるべく高速にタイマーで実行したいけど、標準タイマーの1ミリ秒では遅すぎる・・・といった時に使える感じ。 処理が重い場合は、呼び出し間隔は長くなります。
Chrome と Node.Js で、正しく動いていることを確認していますが、 FirefoxやEdgeでは、まともに動いていなかったので、ご注意下さい。
使い方
使い方は遅延時間に実数を指定できる点を除けば、標準のsetInterval
/clearInterval
と同じです。
遅延時間は標準と同じくミリ秒単位で指定します(0.1=100マイクロ秒)。
以下のように呼び出すことで、タイマーの数と遅延時間等を調整して、定期処理を実行します。
(function() { "use strict"; var ft = require("fractional-timer"); var ftid = ft.setInterval( function(){ ft.clearInterval(ftid); }, 0.001);// 1 microsecond }());
インストール
npmでインストールすればNode.jsから使えます。
> npm install fraction-timer
ブラウザのWEBアプリでは、browserify
でrequire
するか、
script
タグで読み込んで、FractionalTimer
クラスの静的メソッドsetInterval
/clearInterval
を直接使ってくださいね。
リポジトリ
その他、能書き
やっていることは単純です。複数のインターバルタイマーで一つの処理を呼び出しているだけです。
例えば、1マイクロ秒で実行したい場合、1ミリ秒のタイマーを1000個使って呼び出すということです。
実際には、指定された間隔から算出される単位時間あたりの実行回数を計算し、タイマーの数と間隔を自動的に算出、ベストエフォートで呼び出します。
「MZ-700フルJavaScriptエミュレータ」で、エミュレーションをワーカースレッドの定期処理で実装していて、当初は単一のインターバルタイマーで、最も高速になるように調整していたのですが、ある時タイマーが二重に動いてしまったことがあって、速度がなんと2倍程度に跳ね上がっていたのです。
「うわあ!世紀の大発見!!!」と思ったけれど、単一スレッド内ではシーケンシャル動作が保証されているので、当然の挙動なんですよね。
ただ、こういうことをやってる人があまりいないところを見ると、需要はないみたいなんですけどね(笑)
.NETのアプリケーション設定を編集可能なXMLに保存する
.NETのプロジェクトでは「アプリケーション構成ファイル」というファイルに設定情報を書いておけます。 でもあれは実行時に書き換えるような利用方法は想定されていません。 インストーラーでセットアップすると、アプリケーションフォルダに置かれるため、読み取り専用で、編集するにも管理者権限が必要です(また、書き換えたつもりでも、何かのタイミングで元に戻るということも・・・どういう機構なのか知らないのですが・・・)
そこで、ここでは、編集可能なXMLファイルをアプリケーションの動的な設定情報として利用するためのサンプルコードをご紹介。 基本クラスは、お持ち帰り可能です。ご自由にご利用ください。
- はじめに - 機能仕様
- XML設定クラス定義例:SampleSettingsクラス
- 設定ファイルの読み込み処理
- XMLファイルの例:SampleSettings.xml
- XML設定ファイルの基本クラス:XmlSettingFileクラス
- 設定ファイルはWindowsの特殊フォルダ以下へ
- 参考サイト
売り上げランキング: 16,889
1. はじめに - 機能仕様
ここでやりたいことをザックリ以下に箇条書き。
- アプリ起動時に特定のフォルダに保存されたXMLの設定ファイルを読み込む。
- ファイルがない場合は、アプリ起動時に既定の内容で設定ファイルを作成する。
- 設定情報はクラスのインスタンスとしてプログラムからアクセスできる。
- 最低限プリミティブ型が正しく読み書きできること。
設定読み込み時の値のチェックはしていません。 バリデーション機能を持たせたメタクラスを導入すれば可能ですが、大げさになり過ぎるので、またおいおい。
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
に設定ファイルを置くのはちょっと違うかもしれないですからね。