d3.js v5でコンター図を描く

d3.js v5でコンター図を描く

以下のコードでobjcet_functionでコンター図を書きたい対象の関数を定義しています。

x,yはlinspace関数を別途定義してすべての定義域をベクトル化した関数(vect_obj_function)で計算しています。

const margin = {
  top: 20,
  right: 30,
  bottom: 30,
  left: 40
}
const width = 700 - margin.left - margin.right
const height = 400 - margin.top - margin.bottom

const linspace = (start, stop, step = 1) => Array(Math.ceil((stop - start) / step)).fill(start).map((v, i) => v + i * step)
const meshgrid = (x, y) => y.map((y_) => x.map((x_) => [x_, y_])).flat()

// 描画したい関数
const object_function = (x, y) => (x ** 2 + y - 11) ** 2 + (x + y ** 2 - 7) ** 2
const vect_obj_function = x => x.map((v) => object_function(v[0], v[1]))

// 描画したい定義域 linspace(min, max, 刻み値)で変数を作ります
// d3.rangeと機能がかぶってしまっていますが独自定義しています
const x = linspace(-6, 6, 0.1)
const y = linspace(-6, 6, 0.1)

const grid = meshgrid(x, y)

const z = vect_obj_function(grid)

// '等高線'の定義. 等間隔じゃなくて2乗則で決めています
const thresholds = [0.001, ...d3.range(0, 10).map(i => Math.pow(2, i))]

const xScale = d3.scaleLinear(d3.extent(x), [0, width]).nice()
const yScale = d3.scaleLinear(d3.extent(y), [height, 0]).nice()

const transform = ({ type, value, coordinates }) => {
  return {
    type, value,
    coordinates: coordinates.map(rings => {
      return rings.map(points => {
        return points.map(([x, y]) => ([ xScale(x), yScale(y) ]))
      })
    })
  }
}

const contours = d3.contours()
  .size([x.length, y.length])
  .thresholds(thresholds)(z)
  .map(transform)

const color = d3.scaleSequentialLog([d3.max(thresholds), d3.min(thresholds)], d3.interpolateGreys)
const div = d3.select("body")
  .append("div")
  .attr("class", "contour_tooltip")
  .style("font-size", "12px")
  .style("position", "absolute")
  .style("text-align", "center")
  .style("width", "128px")
  .style("height", "34px")
  .style("background", "#333")
  .style("color", "#ddd")
  .style("padding", "0px")
  .style("border", "0px")
  .style("border-radius", "8px")
  .style("opacity", "0");

const svg = d3.select("#fig01")
  .attr("width", width + margin.left + margin.right)
  .attr("height", height + margin.top + margin.bottom)
  .append("g")
  .attr("transform",
    `translate(${margin.left},${margin.top})`)


svg.append("g")
  .attr("fill", "none")
  .attr("stroke", "#fff")
  .selectAll("path")
  .data(contours)
  .join("path")
  .attr("fill", d => color(d.value))
  .attr("stroke", 'white')
  .attr("d", d3.geoPath().projection(d3.geoIdentity()
    .fitSize([width, height], contours[0])))
  .style("stroke-width", 2)

d3.select("#fig01").on("mouseover", () => {
    div.transition().duration(400).style("opacity", 0.9);
    div.style("z-index", "");
  })
  .on("mousemove", () => {
    const point = d3.mouse(d3.event.target)
    const x = xScale.invert(point[0])
    const y = yScale.invert(point[1])
    const z = object_function(x, y)

    div.html(`x = ${x.toFixed(2)} y = ${y.toFixed(2)}<br>f(x,y) = ${z.toFixed(2)}`)
      .style("left", (d3.event.pageX + 20) + "px")
      .style("top", (d3.event.pageY - 35) + "px");
  })
  .on("mouseout", () => {
    div.transition()
      .duration(500)
      .style("opacity", 0);
  })

svg.append("g")
  .attr("transform", `translate(0,${height})`)
  .call(d3.axisBottom(xScale))
svg.append("g")
  .call(d3.axisLeft(yScale))

参考文献

No comments:

Post a Comment