Power BI カスタムビジュアル開発 : SVGUtils で SVG を扱う

# SVGUtils 

`SVGUtils` は SVG の扱いを容易にする機能を提供します。カスタムビジュアルではグラフの描写に SVG を多用するためうまく使うと開発が楽になります。

# インストール

以下手順で追加。

1\. npm コマンドでインストール。

```shell
npm install powerbi-visuals-utils-svgutils --save
```

2\. tsconfig.json の files に型情報を追加。

```json:tsconfig.json
{
  "compilerOptions": {
  ...
  },
  "files": [
    ...,　
    "node_modules/powerbi-visuals-utils-svgutils/lib/index.d.ts"
  ]
}
```

3\. pbiviz.json の externalJS に追加。

```json:pbiviz.json
{
  ...
  "externalJS": [
    ...,
    "node_modules/powerbi-visuals-utils-svgutils/lib/index.js"
  ],
  ...
}
```

# 機能

- [CssConstants](#cssconstants) - クラスセレクターの操作
- [manipulation](#manipulation) - SVG の Transform のサポート
- [Rect](#rect) - 四角形の操作
- [pointer](#pointer) - helps to work with pointer events

# CssConstants

`CssConstants` モジュールはクラスセレクターの操作を容易にする機能を提供。

`powerbi.extensibility.utils.svg.CssConstants` モジュールは以下の機能を提供

- [CreateClassAndSelector](#createclassandselector)

## CreateClassAndSelector

指定した名前で ClassAndSelector インスタンスを作成。作成したインスタンスの .class プロパティを使ってクラスを付与し、.selector をセレクターとして利用。

```typescript
function createClassAndSelector(className: string): ClassAndSelector;
```

### CreateClassAndSelector の例

```typescript
import CssConstants = powerbi.extensibility.utils.svg.CssConstants;

export class Visual implements IVisual {
    ...
    private barClassAndSelector: CssConstants.ClassAndSelector;

    constructor(options: VisualConstructorOptions) {
        ...
        this.barClassAndSelector = CssConstants.createClassAndSelector('bar');
    }

    public update(options: VisualUpdateOptions) {
    ...
    this.bars = this.barContainer
        .selectAll(this.barClassAndSelector.selectorName)
        .data(this.viewModel.dataPoints);
    this.bars.enter()
        .append('rect')
        .classed(this.barClassAndSelector.className, true);
...
```

# manipulation

`manipulation` は SVG で利用できる transform プロパティの値を作成する機能を提供。これによりマジックストリングを減らすことが可能。

`powerbi.extensibility.utils.svg` モジュールは以下の機能を提供。

- [translate](#translate)
- [translateXWithPixels](#translatexwithpixels)
- [translateWithPixels](#translatewithpixels)
- [translateAndRotate](#translateandrotate)
- [scale](#scale)
- [createArrow](#createarrow)

## translate

translate プロパティ用の文字列を作成

```typescript
function translate(x: number, y: number): string
```

### translate の例

```typescript
import svg = powerbi.extensibility.utils.svg;

svg.translate(100, 100); // translate(100,100)
```

## translateXWithPixels

translateX をピクセル単位で作成。

```typescript
function translateXWithPixels(x: number): string
```

### translateXWithPixels の例

```typescript
import svg = powerbi.extensibility.utils.svg;

svg.translateXWithPixels(100); // translateX(100px)
```

## translateWithPixels

ピクセル単位での translate 文字列を作成。

```typescript
function translateWithPixels(x: number, y: number): string
```

### translateWithPixels の例

```typescript
import svg = powerbi.extensibility.utils.svg;

svg.translateWithPixels(100, 100); // translate(100px,100px)
```

## translateAndRotate

translate と rotate の文字列を作成。

```typescript
function translateAndRotate(x: number, y: number, px: number, py: number, angle: number): string
```

### translateAndRotate の例

```typescript
import svg = powerbi.extensibility.utils.svg;

svg.translateAndRotate(100, 100, 50, 50, 35); // translate(100,100) rotate(35,50,50)
```

## scale

CSS 用の scale プロパティを作成。

```typescript
function scale(scale: number): string
```

### scale の例

```typescript
import svg = powerbi.extensibility.utils.svg;

svg.scale(50); // scale(50)
```

## createArrow

Arrow を作成。

```typescript
function createArrow(width: number, height: number, rotate: number): { path: string; transform: string; };
```

### createArrow の例

```typescript
import svg = powerbi.extensibility.utils.svg;

let arrow = svg.createArrow(10, 20, 5);

this.svg.append('path')
    .attr("d",arrow.path)
    .attr("transform", arrow.transform);
```

# Rect
`Rect` は四角形 (Rectangle) の操作を行う機能を提供。

`powerbi.extensibility.utils.svg.shapes.Rect` モジュールは以下の機能を提供。

- [getOffset](#getoffset)
- [getSize](#getsize)
- [setSize](#setsize)
- [right](#right)
- [bottom](#bottom)
- [topLeft](#topleft)
- [topRight](#topright)
- [bottomLeft](#bottomleft)
- [bottomRight](#bottomright)
- [clone](#clone)
- [toString](#tostring)
- [offset](#offset)
- [add](#add)
- [getClosestPoint](#getclosestpoint)
- [equal](#equal)
- [equalWithPrecision](#equalwithprecision)
- [isEmpty](#isempty)
- [containsPoint](#containspoint)
- [isIntersecting](#isintersecting)
- [intersect](#intersect)
- [combine](#combine)
- [getCentroid](#getcentroid)

## getOffset

Rectangle のオフセットを確認。

```typescript
function getOffset(rect: IRect): IPoint;
```

### getOffset の例

```typescript
import Rect = powerbi.extensibility.utils.svg.shapes.Rect;

Rect.getOffset({ left: 25, top: 25, width: 100, height: 100 });

/* returns: {
    x: 25,
    y: 25
}*/
```

## getSize

Rectangle のサイズを確認。

```typescript
function getSize(rect: IRect): ISize;
```

### getSize の例

```typescript
import Rect = powerbi.extensibility.utils.svg.shapes.Rect;

Rect.getSize({ left: 25, top: 25, width: 100, height: 100 });

/* returns: {
    width: 100,
    height: 100
}*/
```

### setSize

rectangle のサイズを変更。

```typescript
function setSize(rect: IRect, value: ISize): void;
```

### setSize の例

```typescript
import Rect = powerbi.extensibility.utils.svg.shapes.Rect;

let rectangle = { left: 25, top: 25, width: 100, height: 100 };

Rect.setSize(rectangle, { width: 250, height: 250 });

// rectangle === { left: 25, top: 25, width: 250, height: 250 }
```

## right

rectangle の右位置を確認。

```typescript
function right(rect: IRect): number;
```

### right の例

```typescript
import Rect = powerbi.extensibility.utils.svg.shapes.Rect;

Rect.right({ left: 25, top: 25, width: 100, height: 100 });

// returns: 125
```

## bottom

rectangle の下位置を確認。

```typescript
function bottom(rect: IRect): number;
```

### bottom の例

```typescript
import Rect = powerbi.extensibility.utils.svg.shapes.Rect;

Rect.bottom({ left: 25, top: 25, width: 100, height: 100 });

// returns: 125
```

## topLeft

rectangle の左上を確認。

```typescript
function topLeft(rect: IRect): IPoint;
```

### topLeft の例

```typescript
import Rect = powerbi.extensibility.utils.svg.shapes.Rect;

Rect.topLeft({ left: 25, top: 25, width: 100, height: 100 });

// returns: { x: 25, y: 25 }
```

## topRight

rectangle の右上を確認。

```typescript
function topRight(rect: IRect): IPoint;
```

### topRight の例

```typescript
import Rect = powerbi.extensibility.utils.svg.shapes.Rect;

Rect.topRight({ left: 25, top: 25, width: 100, height: 100 });

// returns: { x: 125, y: 25 }
```

## bottomLeft

rectangle の左下を確認。

```typescript
function bottomLeft(rect: IRect): IPoint;
```

### bottomLeft の例

```typescript
import Rect = powerbi.extensibility.utils.svg.shapes.Rect;

Rect.bottomLeft({ left: 25, top: 25, width: 100, height: 100 });

// returns: { x: 25, y: 125 }
```

## bottomRight

rectangle の右下を確認。

```typescript
function bottomRight(rect: IRect): IPoint;
```

### bottomRight の例

```typescript
import Rect = powerbi.extensibility.utils.svg.shapes.Rect;

Rect.bottomRight({ left: 25, top: 25, width: 100, height: 100 });

// returns: { x: 125, y: 125 }
```

## clone

rectangle をコピー

```typescript
function clone(rect: IRect): IRect;
```

### clone の例

```typescript
import Rect = powerbi.extensibility.utils.svg.shapes.Rect;

Rect.clone({ left: 25, top: 25, width: 100, height: 100 });

/* returns: {
    left: 25, top: 25, width: 100, height: 100}
*/
```

## toString

rectangle を文字列をとして表現

```typescript
function toString(rect: IRect): string;
```

### toString の例

```typescript
import Rect = powerbi.extensibility.utils.svg.shapes.Rect;

Rect.toString({ left: 25, top: 25, width: 100, height: 100 });

// returns: {left:25, top:25, width:100, height:100}
```

## offset

rectangle に指定したオフセットを適用。

```typescript
function offset(rect: IRect, offsetX: number, offsetY: number): IRect;
```

### offset の例

```typescript
import Rect = powerbi.extensibility.utils.svg.shapes.Rect;

Rect.offset({ left: 25, top: 25, width: 100, height: 100 }, 50, 50);

/* returns: {
    left: 75,
    top: 75,
    width: 100,
    height: 100
}*/
```

## add

1 つ目の rectangle に 2 つ目の rectangle を追加。

```typescript
function add(rect: IRect, rect2: IRect): IRect;
```

### add の例

```typescript
import Rect = powerbi.extensibility.utils.svg.shapes.Rect;

Rect.add(
    { left: 25, top: 25, width: 100, height: 100 },
    { left: 50, top: 50, width: 75, height: 75 });

/* returns: {
    left: 75,
    top: 75,
    height: 175,
    width: 175
}*/
```

## getClosestPoint

指定した rectangle  に近いポイント (IPoint) を確認。

```typescript
function getClosestPoint(rect: IRect, x: number, y: number): IPoint
```

### getClosestPoint の例

```typescript
import Rect = powerbi.extensibility.utils.svg.shapes.Rect;

Rect.getClosestPoint({ left: 0, top: 0, width: 100, height: 100 }, 50, 50);

/* returns: {
    x: 50,
    y: 50
}*/
```

## equal

2 つの rectangle が同じか確認。

```typescript
function equal(rect1: IRect, rect2: IRect): boolean
```

### equal の例

```typescript
import Rect = powerbi.extensibility.utils.svg.shapes.Rect;

Rect.equal(
    { left: 0, top: 0, width: 100, height: 100 },
    { left: 50, top: 50, width: 100, height: 100 });

// returns: false
```

## equalWithPrecision

小数点の精度レベルで 2 つの rectangle が同じか確認。

```typescript
function equalWithPrecision(rect1: IRect, rect2: IRect): boolean
```

### equalWithPrecision の例

```typescript
import Rect = powerbi.extensibility.utils.svg.shapes.Rect;

Rect.equalWithPrecision(
    { left: 0, top: 0, width: 100, height: 100 },
    { left: 50, top: 50, width: 100, height: 100 });

// returns: false
```

## isEmpty

rectangle が空か確認。

```typescript
function isEmpty(rect: IRect): boolean
```

### isEmpty の例

```typescript
import Rect = powerbi.extensibility.utils.svg.shapes.Rect;

Rect.isEmpty({ left: 0, top: 0, width: 0, height: 0 });

// returns: true
```

## containsPoint

rectangle がポイント (IPoint) を含むか確認。

```typescript
function containsPoint(rect: IRect, point: IPoint): boolean
```

### containsPoint の例

```typescript
import Rect = powerbi.extensibility.utils.svg.shapes.Rect;

Rect.containsPoint({ left: 0, top: 0, width: 100, height: 100 }, { x: 50, y: 50 });

// returns: true
```

## isIntersecting

2 つの rectangle が交差しているか確認。

```typescript
function isIntersecting(rect1: IRect, rect2: IRect): boolean
```

### isIntersecting の例

```typescript
import Rect = powerbi.extensibility.utils.svg.shapes.Rect;

Rect.isIntersecting(
    { left: 0, top: 0, width: 100, height: 100 },
    { left: 0, top: 0, width: 50, height: 50 });

// returns: true
```

## intersect

2 つの rectangle が交差する領域を確認。

```typescript
function intersect(rect1: IRect, rect2: IRect): IRect
```

### intersect の例

```typescript
import Rect = powerbi.extensibility.utils.svg.shapes.Rect;

Rect.intersect(
    { left: 0, top: 0, width: 100, height: 100 },
    { left: 0, top: 0, width: 50, height: 50 });

/* returns: {
    left: 0,
    top: 0,
    width: 50,
    height: 50
}*/
```

## combine

2 つの rectangle を重ねた合計を確認。

```typescript
function combine(rect1: IRect, rect2: IRect): IRect
```

### combine の例

```typescript
import Rect = powerbi.extensibility.utils.svg.shapes.Rect;

Rect.combine(
    { left: 0, top: 0, width: 100, height: 100 },
    { left: 0, top: 0, width: 50, height: 120 });

/* returns: {
    left: 0,
    top: 0,
    width: 100,
    height: 120
}*/
```

## getCentroid

rectangle の中心点を取得。

```typescript
function getCentroid(rect: IRect): IPoint
```

### getCentroid の例

```typescript
import Rect = powerbi.extensibility.utils.svg.shapes.Rect;

Rect.getCentroid({ left: 0, top: 0, width: 100, height: 100 });

/* returns: {
    x: 50,
    y: 50
}*/
```

# pointer

`pointer` はポインターの位置を確認する機能を提供。

`powerbi.extensibility.utils.svg` モジュールは以下の機能を提供。

* [getCoordinates](#getcoordinates)

## getCoordinates

現在のポインター位置を返す。

```typescript
function getCoordinates(rootNode: Element, isPointerEvent: boolean): number[]
```

### getCoordinates の例

```typescript
import svg = powerbi.extensibility.utils.svg;

let bodySelection = d3.select("body");

bodySelection
    .append("div")
    .style({
        width: "100px",
        height: "100px",
        "background-color": "green"
    })
    .on("click", () => {
        svg.getCoordinates(bodySelection.node(), true);
    });

// Div をクリックした時点の位置を返す
```

# まとめ

高度なビジュアルでは多くの SVG 要素が含まれ、重なっているもののハンドルなども必要となってきます。

[目次に戻る](https://qiita.com/kenakamu/items/f57ab7f73d6ea10bc9d9)

# 参考
[SVGUtils](https://github.com/Microsoft/powerbi-visuals-utils-svgutils)