Переключение между чисткой и панорамированием / масштабированием по оси X в d3v5 - PullRequest
0 голосов
/ 04 июля 2019

Я использую d3.js версии 5 и нарисовал ось шкалы времени, которая отображает продолжительность в секундах. Эта временная шкала отображает только 60-секундный интервал всей длительности, но можно перемещаться / масштабировать влево / вправо для перемещения по временной шкале. Также нужно уметь выделять несколько временных интервалов с помощью кистей. Код для рисования нескольких кистей основан на https://bl.ocks.org/NGuernse/4c75a051154cbe08bf80cddefefae22a / https://gist.github.com/NGuernse/4c75a051154cbe08bf80cddefefae22a .

Теперь моя проблема в том, что, как только я добавляю код для рисования кистей на временной шкале, я больше не могу выполнять масштабирование / панорамирование на оси временной шкалы.

Подобно ссылкам выше, я добавил переключатель, который отключает функцию кисти, а взамен также должен включить функцию масштабирования. Однако я могу только отключить / включить функцию чистки и никогда не включать функцию масштабирования.

var margin = {top: 200, right: 40, bottom: 200, left: 40},
    width = 960 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;


// Zoom
var zoom = d3.zoom()
    .extent([[0, 0], [width, height]])
    .scaleExtent([1, 10])
    .translateExtent([[0, 0], [width, height]])
    .on('zoom', zoomed);

// Scale
var xScale = d3.scaleTime()
    .domain([0, 137 * 1000])//domain of [00:00 - 02:17] in seconds
    .range([0, width]);

// Axis
var xAxis = d3.axisBottom(xScale)
    .ticks(d3.timeSecond.every(1))
    .tickSize(-height)
    .tickFormat(d3.timeFormat('%M:%S'))


// Main svg
var svg = d3.select('#block')
    .append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
    .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")")

var rect = svg
    .append("rect")
    .attr("width", width)
    .attr("height", height)
    .style("fill", "white")
    .call(zoom);

// axis
var axis = svg.append("g")

var formatSeconds = d3.timeFormat("%S");

// Jump to interval [00:00, 01:00] at start
startTransition();
var ticks = d3.selectAll(".tick text");

// Only display a tick-label if second is dividable by 5
ticks.attr("class", function (d, i) {

    var s = formatSeconds(d)
    if (s % 5 !== 0) d3.select(this).remove();
});

function zoomed() {
    var transform = d3.event.transform;

    var xNewScale = transform.rescaleX(xScale);

    xAxis.scale(xNewScale);
    axis.call(xAxis);
    ticks = d3.selectAll(".tick text")
    // readjust labels in case of zooming
    ticks.attr("class", function (d, i) {
        var s = formatSeconds(d)
        if (s % 5 !== 0) d3.select(this).remove();
    });

}

function startTransition() {

    // to jump to [00:00,01:00] we need to calculate a new scale factor (k)...
    var k = (xScale(137 * 1000) - xScale(0)) / (xScale(60 * 1000) - xScale(0));

    // ...and then a translate to [01:00, 00:00]
    var tx = 0 //- (k * xScale(60));

    var t = d3.zoomIdentity.translate(tx, 0).scale(k);

    // Rescale the axis
    xAxis.scale(t.rescaleX(xScale));
    axis
        .attr("class", "axis axis--x")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis)
        .attr("text-anchor", "middle")
        .selectAll("text")
        .attr("x", 0)


    rect.call(zoom.transform, t);
}

var gBrushes = svg.append('g')
    .attr("height", height)
    .attr("width", width)
    .attr("fill", "none")
    .attr("transform", "translate(" + 0 + "," + 0 + ")")
    .attr("class", "brushes");

var mySelections = {}
var brushCount = 40;

var brushes = [];
newBrush()
drawBrushes()

function newBrush() {
    console.log('newBrush')
    var brush = d3.brushX()
        .extent([[0, 0], [width, height]])

        .on("start", brushstart)
        .on("brush", brushed)
        .on("end", brushend);

    brushes.push({id: brushes.length, brush: brush});


    function brushstart() {
        // Brush start here

    };

    function brushed() {
        let selection = d3.event.selection.map(xScale.invert);
        mySelections[this.id] = {start: selection[0], end: selection[1]};
        // console.log("Selections are: ", mySelections);
    }

    function brushend() {
        // Figure out if our latest brush has a selection
        var lastBrushID = brushes[brushes.length - 1].id;
        var lastBrush = document.getElementById('brush-' + lastBrushID);
        var selection = d3.brushSelection(lastBrush);

        if (!d3.event.sourceEvent) return; // Only transition after input.
        if (!d3.event.selection) return; // Ignore empty selections.
        var d0 = d3.event.selection.map(xScale.invert),
            d1 = d0.map(d3.timeSecond);

        // If empty when rounded, use floor & ceil instead.
        if (d1[0] >= d1[1]) {
            d1[0] = d3.timeSecond.floor(d0[0]);
            d1[1] = d3.timeSecond.offset(d1[0]);
        }

        d3.select(this).transition().call(d3.event.target.move, d1.map(xScale));

        // If it does, that means we need another one
        if (brushes.length < brushCount && selection && selection[0] !== selection[1]) {
            newBrush();
        }

        // Always draw brushes
        drawBrushes();
    }
}

function drawBrushes() {

    var brushSelection = gBrushes
        .selectAll('.brush')
        .data(brushes, function (d) {
            return d.id
        });


    // Set up new brushes
    brushSelection.enter()
        .insert("g", '.brush')
        .attr('class', 'brush')
        .attr('id', function (brush) {
            return "brush-" + brush.id;
        })
        .each(function (brushObject) {
            // call the brush
            brushObject.brush(d3.select(this));


        });


    brushSelection
        .each(function (brushObject) {
            d3.select(this)
                .attr('class', 'brush')
                .selectAll('.overlay')
                .style('pointer-events', function () {
                    var brush = brushObject.brush;
                    if (brushObject.id === brushes.length - 1 && brush !== undefined) {
                        return 'all';
                    } else {
                        return 'none';
                    }
                });
        })

    brushSelection.exit()
        .remove();
}

let toggle = true;

document.getElementById('disable-btn').addEventListener('click', () => {
    toggleBrush();
});

var toggleBrush = function () {
    toggle = !toggle;

    if (!toggle) {
        document.getElementById('disable-btn').innerHTML = '<i class="fa fa-toggle-off"></i> Brushes Off';

        for (let i = 0, len = brushes.length; i < len; i++) {
            d3.select('#brush-' + i).on('.brush', null);
        }

        d3.select('.brushes').selectAll('.selection').style("cursor", "initial");
        d3.select('.brushes').selectAll('.overlay').style("cursor", "initial");
        rect.on(".zoom", null)


    } else {
        document.getElementById('disable-btn').innerHTML = '<i class="fa fa-toggle-on"></i> Brushes On';

        for (let i = 0, len = brushes.length; i < len; i++) {
            brushes[i].brush(d3.select('#brush-' + i));
        }

        rect.call(zoom)
        startTransition();

        d3.select('.brushes').selectAll('.selection').style("cursor", "move");
        d3.select('.brushes').selectAll('.overlay').style("cursor", "crosshair");


    }
};

Вот jsfiddle, включающий только временную шкалу с функцией масштабирования / панорамирования: https://jsfiddle.net/prw67fu3/

А также ссылка на текущую скрипку, которая включает код для рисования кистей и включения / выключения их. https://jsfiddle.net/gb8m4tr3/

После того, как тумблер отключен, необходимо уметь масштабировать / перемещать временную шкалу и больше не рисовать кисти. Однако я не могу понять, как активировать функцию масштабирования, как только добавлен код кисти.

Большое спасибо!

...