d3.js - 将可观察的 d3.js 画笔和焦点示例转换为 jsfiddle

标签 d3.js observable

我在将这个可观察示例转换为 jsfiddle 时遇到问题。

https://observablehq.com/@d3/focus-context?collection=@d3/d3-brush

这是我的 jsfiddle https://jsfiddle.net/u5g1ychz/1/

编辑:最新的 fiddle 尝试 https://jsfiddle.net/03eagvxk/

HTML

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8"/>
</head>
<body>
  <h1>My Chart</h1>
  <div id="my_chart"></div>
</body>
</html>

CSS

.line {
    fill: none;
    stroke: #ffab00;
    stroke-width: 1.5;
}
  
.overlay {
  fill: none;
  pointer-events: all;
}

/* Style the dots by assigning a fill and stroke */
.dot {
    fill: #ffab00;
    stroke: #fff;
}
  
  .focus circle {
  fill: none;
  stroke: steelblue;
}

Javascript

/* new data */
var x = d3.timeDays(new Date(2010, 06, 01), new Date(2020, 10, 30));
var y = Array.from({length: x.length}, Math.random).map(n => Math.floor(n * 10) + 5);
var data = x.map((v, i) => {
  return {
    "date": v,
    "close": y[i]
  }
});

/* begin observable code */
viewof focus = {
  const svg = d3.create("svg")
      .attr("viewBox", [0, 0, width, focusHeight])
      .style("display", "block");

  const brush = d3.brushX()
      .extent([[margin.left, 0.5], [width - margin.right, focusHeight - margin.bottom + 0.5]])
      .on("brush", brushed)
      .on("end", brushended);

  const defaultSelection = [x(d3.utcYear.offset(x.domain()[1], -1)), x.range()[1]];

  svg.append("g")
      .call(xAxis, x, focusHeight);

  svg.append("path")
      .datum(data)
      .attr("fill", "steelblue")
      .attr("d", area(x, y.copy().range([focusHeight - margin.bottom, 4])));

  const gb = svg.append("g")
      .call(brush)
      .call(brush.move, defaultSelection);

  function brushed({selection}) {
    if (selection) {
      svg.property("value", selection.map(x.invert, x).map(d3.utcDay.round));
      svg.dispatch("input");
    }
  }

  function brushended({selection}) {
    if (!selection) {
      gb.call(brush.move, defaultSelection);
    }
  }

  return svg.node();
}

update = {
  const [minX, maxX] = focus;
  const maxY = d3.max(data, d => minX <= d.date && d.date <= maxX ? d.value : NaN);
  chart.update(x.copy().domain(focus), y.copy().domain([0, maxY]));
}

area = (x, y) => d3.area()
    .defined(d => !isNaN(d.value))
    .x(d => x(d.date))
    .y0(y(0))
    .y1(d => y(d.value))
    
x = d3.scaleUtc()
    .domain(d3.extent(data, d => d.date))
    .range([margin.left, width - margin.right])
    
y = d3.scaleLinear()
    .domain([0, d3.max(data, d => d.value)])
    .range([height - margin.bottom, margin.top])
    
xAxis = (g, x, height) => g
    .attr("transform", `translate(0,${height - margin.bottom})`)
    .call(d3.axisBottom(x).ticks(width / 80).tickSizeOuter(0))
    
yAxis = (g, y, title) => g
    .attr("transform", `translate(${margin.left},0)`)
    .call(d3.axisLeft(y))
    .call(g => g.select(".domain").remove())
    .call(g => g.selectAll(".title").data([title]).join("text")
        .attr("class", "title")
        .attr("x", -margin.left)
        .attr("y", 10)
        .attr("fill", "currentColor")
        .attr("text-anchor", "start")
        .text(title))
        
margin = ({top: 20, right: 20, bottom: 30, left: 40})
height = 440
focusHeight = 100

下载可观察的javascript文件

// https://observablehq.com/@d3/focus-context@326
export default function define(runtime, observer) {
  const main = runtime.module();
  const fileAttachments = new Map([["aapl.csv",new URL("./files/de259092d525c13bd10926eaf7add45b15f2771a8b39bc541a5bba1e0206add4880eb1d876be8df469328a85243b7d813a91feb8cc4966de582dc02e5f8609b7",import.meta.url)]]);
  main.builtin("FileAttachment", runtime.fileAttachments(name => fileAttachments.get(name)));
  main.variable(observer()).define(["md"], function(md){return(
md`# Focus + Context

This [area chart](/@d3/area-chart) uses brushing to specify a focused area. Drag the gray region to pan, or brush to zoom. Compare to a [zoomable chart](/@d3/zoomable-area-chart). Data: [Yahoo Finance](https://finance.yahoo.com/lookup)`
)});
  main.variable(observer("chart")).define("chart", ["d3","width","height","DOM","margin","data","xAxis","yAxis","area"], function(d3,width,height,DOM,margin,data,xAxis,yAxis,area)
{
  const svg = d3.create("svg")
      .attr("viewBox", [0, 0, width, height])
      .style("display", "block");

  const clipId = DOM.uid("clip");

  svg.append("clipPath")
      .attr("id", clipId.id)
    .append("rect")
      .attr("x", margin.left)
      .attr("y", 0)
      .attr("height", height)
      .attr("width", width - margin.left - margin.right);

  const gx = svg.append("g");

  const gy = svg.append("g");

  const path = svg.append("path")
      .datum(data)
      .attr("clip-path", clipId)
      .attr("fill", "steelblue");

  return Object.assign(svg.node(), {
    update(focusX, focusY) {
      gx.call(xAxis, focusX, height);
      gy.call(yAxis, focusY, data.y);
      path.attr("d", area(focusX, focusY));
    }
  });
}
);
  main.variable(observer("viewof focus")).define("viewof focus", ["d3","width","focusHeight","margin","x","xAxis","data","area","y"], function(d3,width,focusHeight,margin,x,xAxis,data,area,y)
{
  const svg = d3.create("svg")
      .attr("viewBox", [0, 0, width, focusHeight])
      .style("display", "block");

  const brush = d3.brushX()
      .extent([[margin.left, 0.5], [width - margin.right, focusHeight - margin.bottom + 0.5]])
      .on("brush", brushed)
      .on("end", brushended);

  const defaultSelection = [x(d3.utcYear.offset(x.domain()[1], -1)), x.range()[1]];

  svg.append("g")
      .call(xAxis, x, focusHeight);

  svg.append("path")
      .datum(data)
      .attr("fill", "steelblue")
      .attr("d", area(x, y.copy().range([focusHeight - margin.bottom, 4])));

  const gb = svg.append("g")
      .call(brush)
      .call(brush.move, defaultSelection);

  function brushed({selection}) {
    if (selection) {
      svg.property("value", selection.map(x.invert, x).map(d3.utcDay.round));
      svg.dispatch("input");
    }
  }

  function brushended({selection}) {
    if (!selection) {
      gb.call(brush.move, defaultSelection);
    }
  }

  return svg.node();
}
);
  main.variable(observer("focus")).define("focus", ["Generators", "viewof focus"], (G, _) => G.input(_));
  main.variable(observer("update")).define("update", ["focus","d3","data","chart","x","y"], function(focus,d3,data,chart,x,y)
{
  const [minX, maxX] = focus;
  const maxY = d3.max(data, d => minX <= d.date && d.date <= maxX ? d.value : NaN);
  chart.update(x.copy().domain(focus), y.copy().domain([0, maxY]));
}
);
  main.variable(observer("data")).define("data", ["d3","FileAttachment"], async function(d3,FileAttachment){return(
Object.assign(d3.csvParse(await FileAttachment("aapl.csv").text(), d3.autoType).map(({date, close}) => ({date, value: close})), {y: "↑ Close $"})
)});
  main.variable(observer("area")).define("area", ["d3"], function(d3){return(
(x, y) => d3.area()
    .defined(d => !isNaN(d.value))
    .x(d => x(d.date))
    .y0(y(0))
    .y1(d => y(d.value))
)});
  main.variable(observer("x")).define("x", ["d3","data","margin","width"], function(d3,data,margin,width){return(
d3.scaleUtc()
    .domain(d3.extent(data, d => d.date))
    .range([margin.left, width - margin.right])
)});
  main.variable(observer("y")).define("y", ["d3","data","height","margin"], function(d3,data,height,margin){return(
d3.scaleLinear()
    .domain([0, d3.max(data, d => d.value)])
    .range([height - margin.bottom, margin.top])
)});
  main.variable(observer("xAxis")).define("xAxis", ["margin","d3","width"], function(margin,d3,width){return(
(g, x, height) => g
    .attr("transform", `translate(0,${height - margin.bottom})`)
    .call(d3.axisBottom(x).ticks(width / 80).tickSizeOuter(0))
)});
  main.variable(observer("yAxis")).define("yAxis", ["margin","d3"], function(margin,d3){return(
(g, y, title) => g
    .attr("transform", `translate(${margin.left},0)`)
    .call(d3.axisLeft(y))
    .call(g => g.select(".domain").remove())
    .call(g => g.selectAll(".title").data([title]).join("text")
        .attr("class", "title")
        .attr("x", -margin.left)
        .attr("y", 10)
        .attr("fill", "currentColor")
        .attr("text-anchor", "start")
        .text(title))
)});
  main.variable(observer("margin")).define("margin", function(){return(
{top: 20, right: 20, bottom: 30, left: 40}
)});
  main.variable(observer("height")).define("height", function(){return(
440
)});
  main.variable(observer("focusHeight")).define("focusHeight", function(){return(
100
)});
  main.variable(observer("d3")).define("d3", ["require"], function(require){return(
require("d3@6")
)});
  return main;
}

最佳答案

这很棘手,因为函数是可观察的,而 viewof 返回画笔的 View (最小值和最大值)

Here is a working fiddle



/* new data */
var x = d3.timeDays(new Date(2015, 06, 01), new Date(2020, 10, 30));
var y = Array.from({length: x.length}, Math.random).map(n => Math.floor(n * 10) + 5);
var data = x.map((v, i) => {
  return {
    "date": v,
    "value": y[i]
  }
});

var margin = {top: 20, right: 20, bottom: 30, left: 40}
var height = 440;
var width = 600;
var focusHeight = 100;

var focusedArea = d3.extent(x);

  const svg = d3.select('#my_chart').append("svg")
      .attr("viewBox", [0, 0, width, height])
      .style("display", "block");

  const clipId = {id: "clip"};

  const clip = svg.append("clipPath")
      .attr("id", clipId.id)
    .append("rect")
      .attr("x", margin.left)
      .attr("y", 0)
      .attr("height", height)
      .attr("width", width - margin.left - margin.right);

  const gx = svg.append("g");

  const gy = svg.append("g");

  const path = svg.append("path")
      .datum(data)
      .attr("clip-path", `url(#${clipId.id})`)
      .attr("fill", "steelblue");

  const updateChart = (focusX, focusY) => {
      gx.call(xAxis, focusX, height);
      gy.call(yAxis, focusY, data.y);
      path.attr("d", area(focusX, focusY));
    };


/* begin observable code */
var focus = () => {
  const svg = d3.select("#focus").append("svg")
      .attr("viewBox", [0, 0, width, focusHeight])
      .style("display", "block");
  const brush = d3.brushX()
      .extent([[margin.left, 0.5], [width - margin.right, focusHeight - margin.bottom + 0.5]])
      .on("brush", brushed)
      .on("end", brushended);

  const defaultSelection = [x(d3.utcYear.offset(x.domain()[1], -1)), x.range()[1]];

  svg.append("g")
      .call(xAxis, x, focusHeight);

  svg.append("path")
      .datum(data)
      .attr("fill", "steelblue")
      .attr("d", area(x, y.copy().range([focusHeight - margin.bottom, 4])));

  const gb = svg.append("g")
      .call(brush)
      .call(brush.move, defaultSelection);

  function brushed({selection}) {
    if (selection) {
      svg.property("value", selection.map(x.invert, x).map(d3.utcDay.round));
      svg.dispatch("input");
        focusedArea = svg.property('value');
        update();
    }
  }

  function brushended({selection}) {
    if (!selection) {
      gb.call(brush.move, defaultSelection);
    }
  }
  return svg.node();
}

var update = function() {
  const [minX, maxX] = focusedArea;
  const maxY = d3.max(data, d => minX <= d.date && d.date <= maxX ? d.value : NaN);
  updateChart(x.copy().domain(focusedArea), y.copy().domain([0, maxY]));
}

var area = (x, y) => d3.area()
    .defined(d => !isNaN(d.value))
    .x(d => x(d.date))
    .y0(y(0))
    .y1(d => y(d.value))
    
var x = d3.scaleUtc()
    .domain(d3.extent(data, d => d.date))
    .range([margin.left, width - margin.right])
    
var y = d3.scaleLinear()
    .domain([0, d3.max(data, d => d.value)])
    .range([height - margin.bottom, margin.top])
    
var xAxis = (g, x, height) => g
    .attr("transform", `translate(0,${height - margin.bottom})`)
    .call(d3.axisBottom(x).ticks(width / 80).tickSizeOuter(0))
    
var yAxis = (g, y, title) => g
    .attr("transform", `translate(${margin.left},0)`)
    .call(d3.axisLeft(y))
    .call(g => g.select(".domain").remove())
    .call(g => g.selectAll(".title").data([title]).join("text")
        .attr("class", "title")
        .attr("x", -margin.left)
        .attr("y", 10)
        .attr("fill", "currentColor")
        .attr("text-anchor", "start")
        .text(title))
        



focus();

关于d3.js - 将可观察的 d3.js 画笔和焦点示例转换为 jsfiddle,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65896903/

相关文章:

javascript - 如何在 Angular 组件中显示来自 Observable 的数据

rxjs - .unsubscribe 和 .take(1) 之间的区别

angular - 类型 '{Property : string, Property2: string} 不可分配给类型 Observable<Filters[]>

javascript - 无法将工具提示 header 样式化为 'pull-left'

javascript - JSON 中出现次数的条形图

javascript - 如何 trim/删除嵌套/树状JSON中的节点?

Angular HttpClient 和 Observables

javascript - Knockout observable 数组,在更改事件时获取更新的项目值

javascript - D3.js 数据源切换时的过渡条形图

javascript - d3.js:5942 错误:<g> 属性转换的无效值 ="translate(NaN,0)"