Я использую 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/
После того, как тумблер отключен, необходимо уметь масштабировать / перемещать временную шкалу и больше не рисовать кисти. Однако я не могу понять, как активировать функцию масштабирования, как только добавлен код кисти.
Большое спасибо!