Skip to content

Instantly share code, notes, and snippets.

@yano3nora
Last active March 23, 2024 09:53
Show Gist options
  • Save yano3nora/770ec02c68ca0b0151429fba8f3192c3 to your computer and use it in GitHub Desktop.
Save yano3nora/770ec02c68ca0b0151429fba8f3192c3 to your computer and use it in GitHub Desktop.
SVG Libraries - react-svg / svg.js / plotly.js #js

react-svg

https://github.com/tanem/react-svg

  • svg ファイルを react component として使えるやつ
  • afterInjection から描画後の node 操作が可能
  • src の svg ファイルから width, height を読み取って viewBox 指定までやってくれるので、中に svg やら html (foreign object) やら nest してやれば resize 時に全体がよき感じに縮尺される
$ npm i react-svg
import React from 'react'
import { ReactSVG } from 'react-svg'

export const Component = () => {
  return <ReactSVG
    src={'/some/file.svg'}
    afterInjection={svgNode => {
      try {
        console.log(svgNode)
      } catch (e) {
        // 内部で throw されると握りつぶされるっぽいのでエラー出してあげてるだけ
        console.error(e)
      }
    }}
  />
}

SVG.js

https://github.com/svgdotjs/svg.js
https://svgjs.dev/docs/3.0/

  • 有名っぽい svg 操作 library で普通に手続き型っぽく操作する感じ
  • 一旦 SVG(node) で wrap してからうにょる感じ
  • svg element の native property, method 習熟してないなら便利
$ npm i @svgdotjs/svg.js
import { SVG } from '@svgdotjs/svg.js'

/**
 * svg の中に矩形の座標・サイズ指定しつつ
 * textarea としての div をいれる、みたいな
 */
export const insertTextarea2Svg = ({
  parent,
  text,
  box,
  styles,
  ruler,
  noCentered,
  scrollable,
}: {
  parent: SVGSVGElement
  text: string
  box: { x: number, y: number, width: number, height: number }
  styles?: Partial<CSSStyleDeclaration>
  ruler?: true
  noCentered?: true
  scrollable?: true
}) => {
  const svg = SVG(parent)
  const { x, y, width, height } = box
  const foreign = svg.foreignObject(width, height).attr({ x, y })
  const div = document.createElement('div')

  div.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml')
  div.style.fontSize = '14px'
  div.style.overflow = scrollable ? 'auto' : 'visible'
  div.style.height = '100%'
  div.style.border = ruler ? '1px solid red' : '1px solid rgba(0,0,0,0)'

  if (!noCentered) {
    div.style.display = 'flex'
    div.style.justifyContent = 'center'
    div.style.alignItems = 'center'
  }

  div.innerHTML = text
  Object.entries(styles || {}).forEach(([key, value]) => {
    // @ts-ignore
    div.style[key] = value
  })

  foreign.add(SVG(div))
}

Insert Graph into SVG by Plotly.js with SVG.js and React-SVG

https://github.com/plotly/plotly.js
https://plotly.com/javascript/
https://x.com/norach7746/status/1721013209476518177?s=20

  • plotly 自体は python や R などでも使えるグラフ生成ライブラリ
  • こいつはその js 版で、simple x plain な plot が生成できる
  • newPlot().then() で node を拾えるので、そこから svg.js と組み合わせて svg の中にグラフを挿入する ことができるという例
    • 加えて react-svg の afterInjection 経由なら、viewBox も適用されてるので responsive & dynamic な graph on svg が出来る、というアイデア
  • window 依存で ssr ng なので csr 必須
$ npm i plotly.js-dist
$ npm i -D @types/plotly.js-dist-min
import { SVG } from '@svgdotjs/svg.js'
import { plotly } from 'libs/plotly'
import { Data as PlotlyData, Root as PlotlyRoot, Layout } from 'plotly.js-dist-min'

export const insertGraph2Svg = ({ parent, graph, box, ruler }: {
  parent: SVGSVGElement
  graph: {
    root: PlotlyRoot
    data: PlotlyData[]
    layout?: Partial<Layout>
  }
  box: { x: number, y: number, width: number, height: number }
  ruler?: true
}) => {
  const svg = SVG(parent)
  const { x, y, width, height } = box

  plotly.newPlot(
    /**
     * html node しか受け付けてないのでお目当ての svg (parent) ではなく
     * 適当な hidden div を root として一度そっちに吐いちゃう
     */
    graph.root,
    graph.data,
    { width, height, ...graph.layout },
    { staticPlot: true },
  ).then(dom => {
    /**
     * dom まるごと foreignObject で wrap して入れる感じ
     */
    const plot = SVG(dom)
    const foreign = svg.foreignObject(width, height).attr({ x, y })

    if (ruler) {
      plot.node.style.outline = '1px solid red'
    }

    plot.attr({ x, y })
    foreign.add(SVG(plot))
  })
}
import React, { useRef } from 'react'
import { ReactSVG } from 'react-svg'

export const Component = () => {
  const ref = useRef(null)

  return <>
    <div ref={ref} style={{ display: 'none' }} />
    {
      ref.current &&
      <ReactSVG
        src={'/some/file.svg'}
        afterInjection={svgNode => {
          try {
            insertGraph2Svg({
              parent: svgNode,
              box: { x: 0, y: 0, width: 300, height: 150 },
              graph: {
                root: ref.current,
                data: [{
                  x: [1, 2, 3, 4, 5],
                  y: [1, 2, 4, 8, 16],
                }],
                layout: {
                  font: { size: 7 },
                  margin: { l: 30, r: 10, t: 15, b: 20, pad: 0 },
                  showlegend: false,
                  title: {
                    text: 'title',
                    yanchor: 'top',
                    font: { size: 7 }k
                  },
                  xaxis: {
                    title: { text: 'x-title', standoff: 0 },
                  },
                  yaxis: {
                    title: { text: 'y-title', standoff: 0 },
                  },
                }
              },
            })
          } catch (e) {
            console.error(e)
          }
        }}
      />
    }
  </>
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment