d3f c - перекрестие с привязкой с использованием последней версии 14 - PullRequest
3 голосов
/ 21 января 2020

В предыдущей версии d3f c мой код использовал fc.util.seriesPointSnapXOnly для привязки перекрестия.

Похоже, что это было в последней версии d3f c (или, возможно, я пропустил это в одном из автономных пакетов?).

Я использую реализацию canvas (annotationCanvasCrosshair), и, кажется, также отсутствует функция «snap», где она ранее использовалась, например:

fc.tool.crosshair()
 .snap(fc.util.seriesPointSnapXOnly(line, series))

Кроме того, «on» также недоступно, поэтому я не могу прикрепить такие события, как trackingstart, trackingend и т. Д. c.

Как я могу теперь реализовать перекрестное перекрестное замыкание? В холстовой версии компонентов не хватает примеров. У кого-нибудь есть пример, показывающий щелчок перекрестия в последней версии d3f c с помощью рендеринга холста?

Вот что у меня есть https://codepen.io/parliament718/pen/xxbQGgp

1 Ответ

1 голос
/ 11 февраля 2020

Я понимаю, что вы подняли проблему с d3f c github, поэтому я предполагаю, что вы знаете, что util/snap.js устарело .

Поскольку эта функциональность сейчас не поддерживается, кажется, что единственный реальный способ обойти это - реализовать свой собственный.

Я взял ваши ручки и исходная привязка. js код в качестве отправной точки и применение метода, описанного в Простой пример перекрестия из документации.

В итоге мне пришлось дословно добавить отсутствующие функции и их зависимости. (конечно, вы можете выполнить рефакторинг и упаковать его в отдельный модуль):

function defined() {
    var outerArguments = arguments;
    return function(d, i) {
        for (var c = 0, j = outerArguments.length; c < j; c++) {
            if (outerArguments[c](d, i) == null) {
                return false;
            } 
        }
        return true;
    };
}

function minimum(data, accessor) {
    return data.map(function(dataPoint, index) {
        return [accessor(dataPoint, index), dataPoint, index];
    }).reduce(function(accumulator, dataPoint) {
        return accumulator[0] > dataPoint[0] ? dataPoint : accumulator;
    }, [Number.MAX_VALUE, null, -1]);
}

function pointSnap(xScale, yScale, xValue, yValue, data, objectiveFunction) {
    // a default function that computes the distance between two points
    objectiveFunction = objectiveFunction || function(x, y, cx, cy) {
        var dx = x - cx,
            dy = y - cy;
        return dx * dx + dy * dy;
    };

    return function(point) {
        var filtered = data.filter(function(d, i) {
            return defined(xValue, yValue)(d, i);
        });

        var nearest = minimum(filtered, function(d) {
            return objectiveFunction(point.x, point.y, xScale(xValue(d)), yScale(yValue(d)));
        })[1];

        return [{
            datum: nearest,
            x: nearest ? xScale(xValue(nearest)) : point.x,
            y: nearest ? yScale(yValue(nearest)) : point.y
        }];
    };
}

function seriesPointSnap(series, data, objectiveFunction) {
    return function(point) { 
        var xScale = series.xScale(),
            yScale = series.yScale(),
            xValue = series.crossValue(),
            yValue = (series.openValue).call(series);
        return pointSnap(xScale, yScale, xValue, yValue, data, objectiveFunction)(point);
    };
};

function seriesPointSnapXOnly(series, data) {
    function objectiveFunction(x, y, cx, cy) {
        var dx = x - cx;
        return Math.abs(dx);
    }
    return seriesPointSnap(series, data, objectiveFunction);
}

Рабочий конечный результат можно увидеть здесь: https://codepen.io/timur_kh/pen/YzXXOOG. Я в основном определил две серии и использовал компонент pointer для обновления данных второй серии и запуска повторного рендеринга:

    const data = {
      series: stream.take(50), // your candle stick chart
      crosshair: [] // second series to hold the crosshair position
    };
    .............
    const crosshair = fc.annotationCanvasCrosshair() // define your crosshair

    const multichart = fc.seriesCanvasMulti()
            .series([candlesticks, crosshair]) // we've got two series now
      .mapping((data, index, series) => {
      switch(series[index]) {
        case candlesticks:
          return data.series;
        case crosshair:
          return data.crosshair;
      }
    });
    .............
function render() {
  d3.select('#zoom-chart')
    .datum(data)
    .call(chart);  
    // add the pointer component to the plot-area, re-rendering each time the event fires.
  var pointer = fc.pointer()
    .on('point', (event) => {     
      data.crosshair = seriesPointSnapXOnly(candlesticks, data.series)(event[0]);// and when we update the crosshair position - we snap it to the other series using the old library code.
      render();
    });

  d3.select('#zoom-chart .plot-area')
    .call(pointer); 
}

UPD : функциональность может быть упрощена следующим образом: я также обновил ручку:

function minimum(data, accessor) {
    return data.map(function(dataPoint, index) {
        return [accessor(dataPoint, index), dataPoint, index];
    }).reduce(function(accumulator, dataPoint) {
        return accumulator[0] > dataPoint[0] ? dataPoint : accumulator;
    }, [Number.MAX_VALUE, null, -1]);
}

function seriesPointSnapXOnly(series, data, point) { 
        if (point == undefined) return []; // short circuit if data point was empty
        var xScale = series.xScale(),
            xValue = series.crossValue();

        var filtered = data.filter((d) => (xValue(d) != null));
        var nearest = minimum(filtered, (d) => Math.abs(point.x - xScale(xValue(d))))[1]; 

        return [{
            x: xScale(xValue(nearest)),
            y: point.y 
        }];
    };

Это далеко не отполировано, но я надеюсь, что оно передает общую идею.

...