D3 js如何在X轴的标签中获取纯文本并制作值scaleLog

D3 js How to get plain text in label at X axis and make value scaleLog

嗨,我想出了如何在 Y 轴上添加日志标签,但无法制作图表线比例日志,而且我真的不知道如何制作 X 轴标签纯文本而不是将其转换为日期

如果我只是将 scaleLinear 更改为 scaleLog 图表线消失

同时删除 const parseTime = d3.timeParse('%Y/%m/%d');并将 parseTime(val.date) 更改为 val.date,将 scaleTime 更改为 scaleOrdinal 并不像我想的那样工作

我也不明白为什么Y轴标签这么模糊

抱歉提问,但无法了解此插件的工作原理。 使用 D3 7.2.1 和 JQ 3.5.1

(function (d3){
        const lineChartData = [
        {
            currency: "data",
            values: [
            {
                date: "2018/01/01",
                close: 0
            },
            {
                date: "2018/02/01",
                close: 5
            },
            {
                date: "2018/03/01",
                close: 10 
            },
            {
                date: "2018/04/01",
                close: 50 
            },
            {
                date: "2018/05/01",
                close: 100 
            },
            {
                date: "2018/06/01",
                close: 500 
            },
            {
                date: "2018/07/01",
                close: 1000 
            },
            {
                date: "2018/08/01",
                close: 5000 
            },
            {
                date: "2018/09/01",
                close: 10000 
            },
            ]
        }
        ];
    
        const margin = {
        top: 20,
        bottom: 20,
        left: 50,
        right: 20
        };
    
        const width = 400 - margin.left - margin.right;
        const height = 300 - margin.top - margin.bottom;
        
        const createGradient = select => {
        const gradient = select
            .select('defs')
            .append('linearGradient')
                .attr('id', 'gradient')
                .attr('x1', '0%')
                .attr('y1', '100%')
                .attr('x2', '0%')
                .attr('y2', '0%');

        gradient
                .append('stop')
                .attr('offset', '0%')
                .attr('style', 'stop-color:#FF6500; stop-opacity:0');

        gradient
            .append('stop')
            .attr('offset', '100%')
            .attr('style', 'stop-color:#FF6500; stop-opacity: 1');
        }
        
        const createGlowFilter = select => {
        const filter = select
            .select('defs')
            .append('filter')
                .attr('id', 'glow')
    
        //stdDeviation is px count for make blur around main chart line
        filter
            .append('feGaussianBlur')
            .attr('stdDeviation', '0')
            .attr('result', 'coloredBlur');
        
        const femerge = filter
            .append('feMerge');
        
        femerge
            .append('feMergeNode')
            .attr('in', 'coloredBlur');
        femerge
            .append('feMergeNode')
            .attr('in', 'SourceGraphic');
        }
    
        const svg = d3.select('#line-chart')
        .append('svg')
            .attr('width', 700 + margin.left + margin.right)
            .attr('height', 300 + margin.top + margin.bottom)
        .append('g')
            .attr('transform', `translate(${margin.left}, ${margin.top})`);
        
        svg.append('defs');
        svg.call(createGradient);
        svg.call(createGlowFilter);
    
        const parseTime = d3.timeParse('%Y/%m/%d');
        
        const parsedData = lineChartData.map(company => ({
        ticker: company.ticker,
        values: company.values.map(val => ({
            close: val.close,
            date: parseTime(val.date)
        }))
        }));
    
        const xScale = d3.scaleTime()
        .domain([
            d3.min(parsedData, d => d3.min(d.values, v => v.date)),
            d3.max(parsedData, d => d3.max(d.values, v => v.date))
        ])
        .range([0, width]);

        const yScale = d3.scaleLinear()
        .domain([
            d3.min(parsedData, d => d3.min(d.values, v => v.close)),
            d3.max(parsedData, d => d3.max(d.values, v => v.close))
        ])
        .range([height, 0]);
        
        const line = d3.line()
        .x(d => xScale(d.date))
        .y(d => yScale(d.close))
        .curve(d3.curveCatmullRom.alpha(0.5));
        
        svg.selectAll('.line')
        .data(parsedData)
        .enter()
        .append('path')
            .attr('d', d => {
            const lineValues = line(d.values).slice(1);
            const splitedValues = lineValues.split(',');
        
            return `M0,${height},${lineValues},l0,${height - splitedValues[splitedValues.length - 1]}`
            })
            .style('fill', 'url(#gradient)')
        
        svg.selectAll('.line')
        .data(parsedData)
        .enter()
        .append('path')
            .attr('d', d => line(d.values))
            .attr('stroke-width', '2')
            .style('fill', 'none')
            .style('filter', 'url(#glow)')
            .attr('stroke', '#FF6500');

        const tick = svg.append('g')
        .attr('transform', `translate(0, ${height})`)
        .call(d3.axisBottom(xScale).ticks(9))
        .selectAll('.tick')
        .style('transition', '.2s');
    
        //Y dashes
        //stroke color of line in background
        //stroke-dasharray
        //first paramter is length
        //second parameter is space between
        tick
        .selectAll('line')
            .attr('stroke-dasharray', `4, 7`)
            .attr('stroke', '#5E779B')
            .attr('y2', `-${height}px`)
        tick
        .append('rect')
            .attr('width', `${(width / 12) + 10}px`)
            .attr('x', `-${width / 24 + 5}px`)
            .attr('y', `-${height}px`)
            .attr('height', `${height + 30}px`)
            .style('fill', 'transparent');
            
        svg.selectAll('.tick')
        .append('circle')
            .attr('r', '5px')
            .style('fill', '#ffffff')
            .style('stroke', '#FF6500')
            .attr('cy', (x, i) => - height + yScale(parsedData[0].values[i].close));


        svg.select('.domain')
        .attr('stroke', '#5E779B')
        .attr('stroke-dasharray', `4, 7`)

        var yscale = d3.scaleLog()
            .domain([1, 100000])
                        .nice()
            .range([height - 10, -10]);

        var y_axis = d3.axisLeft(yscale);

        y_axis.ticks(5);
  
        svg.append("g")
            .call(d3.axisLeft(xScale).ticks(5))
            .attr("transform", "translate(0, 10)")
            .attr('stroke', '#5E779B')
            .attr('stroke-dasharray', `4, 7`)
            .call(y_axis)
    })
    (d3);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

<body>
    <div id="line-chart"></div>
</body>

我将从对数刻度问题开始。您有两个 y 尺度,yScaleyscale。第一个是线性标尺,用于线生成器并定位圆圈。第二个是仅用于轴的对数刻度。您应该只有一个 y 尺度并将其用于定位元素和轴。

此外,log scales 无法处理值 0:

As log(0) = -∞, a log scale domain must be strictly-positive or strictly-negative; the domain must not include or cross zero.

收盘值为 0 的数据点无法在对数刻度上显示。

接下来,由于您在 y 轴组上设置的“stroke”和“stroke-dasharray”属性,y 轴标签看起来不对。

最后,对于 x 尺度,将字符串转换为 Date 对象并使用 d3.scaleTime 是正确的。对于 x 轴,您可以执行以下操作:

const xAxis = d3.axisBottom(xScale)
    .ticks(d3.timeMonth.every(1), '%b');

ticks says that you want to have one tick for each month. The second argument is a date format specifier 的第一个参数定义了刻度标签的格式。 %b 在每个刻度线处放置一个缩写的月份名称。如果您希望刻度线与原始字符串具有相同的格式,那么您可以使用 %Y/%m/%d,但您可能会发现标签太长并且相互重叠。

这是一个解决上述问题的示例。

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <script src="https://d3js.org/d3.v7.js"></script>
</head>

<body>
  <div id="line-chart"></div>

  <script>
    // data

    const lineChartData = [
      {
        ticker: "ABC",
        values: [
          {
            date: "2018/01/01",
            close: 1
          },
          {
            date: "2018/02/01",
            close: 5
          },
          {
            date: "2018/03/01",
            close: 10
          },
          {
            date: "2018/04/01",
            close: 50
          },
          {
            date: "2018/05/01",
            close: 100
          },
          {
            date: "2018/06/01",
            close: 500
          },
          {
            date: "2018/07/01",
            close: 1000
          },
          {
            date: "2018/08/01",
            close: 5000
          },
          {
            date: "2018/09/01",
            close: 10000
          },
        ]
      }
    ];

    const parseTime = d3.timeParse('%Y/%m/%d');

    const parsedData = lineChartData.map(company => ({
      ticker: company.ticker,
      values: company.values.map(val => ({
        close: val.close,
        date: parseTime(val.date)
      }))
    }));

    // gradient
    const createGradient = defs => {
      const gradient = defs.append('linearGradient')
          .attr('id', 'gradient')
          .attr('x1', '0%')
          .attr('y1', '100%')
          .attr('x2', '0%')
          .attr('y2', '0%');

      gradient.append('stop')
          .attr('offset', '0%')
          .attr('style', 'stop-color:#FF6500; stop-opacity:0');

      gradient.append('stop')
          .attr('offset', '100%')
          .attr('style', 'stop-color:#FF6500; stop-opacity: 1');
    }

    // filter
    const createGlowFilter = defs => {
      const filter = defs.append('filter')
          .attr('id', 'glow')

      //stdDeviation is px count for make blur around main chart line
      filter.append('feGaussianBlur')
          .attr('stdDeviation', '2')
          .attr('result', 'coloredBlur');

      const femerge = filter.append('feMerge');

      femerge.append('feMergeNode')
          .attr('in', 'coloredBlur');

      femerge.append('feMergeNode')
          .attr('in', 'SourceGraphic');
    }

    // set up

    const margin = { top: 20, right: 20, bottom: 20, left: 50 };

    const width = 400 - margin.left - margin.right;
    const height = 300 - margin.top - margin.bottom;

    const svg = d3.select('#line-chart')
      .append('svg')
        .attr('width', width + margin.left + margin.right)
        .attr('height', height + margin.top + margin.bottom)
        .call(svg => svg.append('defs')
            .call(createGradient)
            .call(createGlowFilter))
      .append('g')
        .attr('transform', `translate(${margin.left},${margin.top})`);

    // scales

    const xScale = d3.scaleTime()
        .domain([
          d3.min(parsedData, d => d3.min(d.values, v => v.date)),
          d3.max(parsedData, d => d3.max(d.values, v => v.date))
        ])
        .range([0, width]);

    const yScale = d3.scaleLog()
        .domain([
          d3.min(parsedData, d => d3.min(d.values, v => v.close)),
          d3.max(parsedData, d => d3.max(d.values, v => v.close))
        ])
        .range([height, 0]);

    // line and area generators

    const area = d3.area()
        .x(d => xScale(d.date))
        .y1(d => yScale(d.close))
        .y0(yScale(yScale.domain()[0]))
        .curve(d3.curveCatmullRom.alpha(0.5));

    const line = area.lineY1();

    // axes

    const xAxis = d3.axisBottom(xScale)
        .ticks(d3.timeMonth.every(1), '%b');

    svg.append('g')
        .attr('transform', `translate(0,${height})`)
        .call(xAxis)
        // add vertical grid lines
        .call(g =>
          g.selectAll('.tick>line')
            .clone()
              .attr('stroke', '#5E779B')
              .attr('stroke-dasharray', '4, 7')
              .attr('y0', 0)
              .attr('y1', -height)
        )

    const yAxis = d3.axisLeft(yScale)
        .ticks(5);

    svg.append('g')
        .call(yAxis);

    // draw area, line, and circles

    // create one group for each company
    const companies = svg.append('g')
      .selectAll('g')
      .data(parsedData)
      .join('g');

    // add the area
    companies.append('path')
        .attr('d', d => area(d.values))
        .attr('fill', 'url(#gradient)');

    // add the line
    companies.append('path')
        .attr('d', d => line(d.values))
        .attr('stroke-width', 2)
        .attr('fill', 'none')
        .attr('stroke', '#FF6500')
        .attr('filter', 'url(#glow)');

    // add the circles
    companies.selectAll('circle')
      .data(d => d.values)
      .join('circle')
        .attr('fill', 'white')
        .attr('stroke', '#FF6500')
        .attr('r', '3')
        .attr('cx', d => xScale(d.date))
        .attr('cy', d => yScale(d.close));
  </script>
</body>

</html>