首页 > 解决方案 > 如何“平滑” D3.js 折线图?(边缘非常锯齿)

问题描述

我有一个我非常满意的 D3 折线图,但我正在努力寻找一种方法来“平滑”折线图。(欢迎您在bravos.co上查看生产中的图表。

线条有尖锐的点,特别是当时间序列波动或快速上升和下降时。

有什么方法可以对其应用“平滑”效果,或者其他一些使线条看起来不那么“锯齿状”的方法?

(我很抱歉,通常我会把它放在 Fiddle 中,除了有许多依赖项和 API 调用。)谢谢!

D3 图表有锯齿状边缘

constructor(props) {
    super(props)

    this.state = {
        id: this.props.id,
        change: this.props.change,
        style: {},
        showTooltip: false,
        label: '',
        value: '',
    }

    this.chartWidth = this.props.width || CHART_WIDTH
    this.chartHeight = this.props.height || CHART_HEIGHT
    this._ref = null
    this._svg = null
    this._chartData = props.chart

    // prevent crashes when no chart data is provided
    if (!this._chartData || this._chartData.length===0) this._chartData = []


    // prepare chart data
    const { previousClose } = props
    let value = null
    let item = null
    let tmpValue = null
    // normalize the data
    for(let i=0; i < this._chartData.length; i++) {
        item = this._chartData[i]
        // detect crypto vs public
        item.time = item.minute ? parseTime(item.minute) : item.timestamp
        value = item.marketOpen || item.price
        if (value && value !== 0 && value !== -1) {
            item.marketOpen = value
            tmpValue = value
        } else if (tmpValue) {
            item.marketOpen = tmpValue
        } else {
            item.marketOpen = null
        }
    }
    // filer out null values (corrupted data)
    this._chartData = this._chartData.filter((item) => (item.marketOpen !== null))


    // define min/max values for tooltip info
    let minValue = {disabled:true, marketOpen: this._chartData[0] && this._chartData[0].marketOpen}
    let maxValue = {disabled:true, marketOpen: this._chartData[0] && this._chartData[0].marketOpen}
    for(let i=0; i < this._chartData.length; i++) {
        item = this._chartData[i]
        if (item.marketOpen < minValue.marketOpen) minValue = item
        if (item.marketOpen > maxValue.marketOpen) maxValue = item
    }

    // define _keyPointsData array for tooltip info
    this._keyPointsData = []
    if (this._chartData.length>0) {
        this._keyPointsData.push(this._chartData[0])
        this._keyPointsData[this._keyPointsData.length-1].tooltipLabel = 'Open'
    }
    if (this._chartData.length>1) {
        this._keyPointsData.push(this._chartData[this._chartData.length-1])
        this._keyPointsData[this._keyPointsData.length-1].tooltipLabel = 'Close'
    }
    if (this._chartData.length>3 && !minValue.disabled) {
        this._keyPointsData.push(minValue)
        this._keyPointsData[this._keyPointsData.length-1].tooltipLabel = 'Low'
    }
    if (this._chartData.length>3 && !maxValue.disabled) {
        this._keyPointsData.push(maxValue)
        this._keyPointsData[this._keyPointsData.length-1].tooltipLabel = 'High'
    }

    // calculate variable graph height
    let interpolatedChartHeight = this.chartHeight
    if (!this.props.crypto) { // use interpolated Y scale
        interpolatedChartHeight =  Math.min( Math.pow(Math.abs(this.props.changePercent*4),(1/3)), 1) * this.chartHeight
    } else {
        interpolatedChartHeight =  Math.min( Math.pow(Math.abs(this.props.changePercent*4),(1/3)), 1) * this.chartHeight
    }
    let interpolatedChartWidth = this.chartWidth
    if (!this.props.crypto) {
        interpolatedChartWidth = this.chartWidth * this._chartData.length/X_INTERPOLATION_TOTAL
    }
    this._interpolatedChartHeight = interpolatedChartHeight
    this._interpolatedChartWidth = interpolatedChartWidth

    // prepare data domain
    let added = false
    if (this._chartData[0]) {
        // add to domain for proper prev close line placement
        added = true
        this._chartData.unshift({time:this._chartData[0].time, marketOpen:previousClose})
    }
    this.xDomain = d3.extent(this._chartData, d => d.time)
    this.yDomain = d3.extent(this._chartData, d => d.marketOpen)
    if (added) this._chartData.shift() // remove it so it's not showing on the chart

    // define data scale
    this.xScale = d3.scaleTime()
                    .rangeRound([0, interpolatedChartWidth])
                    .domain(this.xDomain)
    this.yScale = d3.scaleLinear()
                    .rangeRound([interpolatedChartHeight, 0])
                    .domain(this.yDomain)

    // define line function
    this.line = d3.line()
        .x(d => this.xScale(d.time) )
        .y(d => this.yScale(d.marketOpen) )

    // define area function
    this.area = d3.area()
        .x(d => this.xScale(d.time) )
        .y0(d => this.yScale(d.marketOpen >= previousClose ? d.marketOpen : previousClose) )
        .y1(d => this.yScale(d.marketOpen <= previousClose ? d.marketOpen : previousClose) )

    // store chart scaled coordinates for easy tooltip generation
    this._keyPointsData.forEach(d => d.pos={x: this.xScale(d.time), y: this.yScale(d.marketOpen)} )
}

标签: d3.js

解决方案


推荐阅读