Reputation: 201
I added some gridlines to my charts. Now my problem is that the grid lines are not aligned to the chart.
Also there is a last grid line added on the end of the chart on the right side is there a possibility to prevent that and only add a line to each label on the x axis?
I tried to add the gird lines to the clip path somehow but it didn't work.
My grid lines are getting created this way:
var signalData = {
signal1: {
name: "signal1",
data: [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 1, 2, 3, 5, 4, 1, 4, 2, 9, 7, 5, 7, 4, 6],
},
signal2: {
name: "signal2",
data: [6, 4, 8, 5, 4, 8, 4, 3, 5, 4, 5, 8, 7, 2, 9, 5, 4, 1, 2, 6, 0, 5, 7, 1],
},
signal3: {
name: "signal3",
data: [9, 5, 12, 3, 8, 4, 8, 6, 3, 4, 7, 8, 5, 2, 1, 8, 6, 8, 5, 8, 4, 8, 5, 1],
},
}
var margin = {
top: 10,
right: 50,
bottom: 40,
left: 50,
};
var width = window.innerWidth - margin.left - margin.right;
var height = 230 - margin.top - margin.bottom;
var minimapHeight = 150 - margin.top - margin.bottom;
var xScale = d3
.scaleLinear()
.domain([
0,
d3.max(Object.keys(signalData), (d) => signalData[d].data.length),
]) // input
.range([0, width]); // output
var brushXScale = d3
.scaleLinear()
.domain([
0,
d3.max(Object.keys(signalData), (d) => signalData[d].data.length),
]) // input
.range([0, width]); // output
var zoomBrush = d3
.brushX()
.extent([
[0, 0],
[width, minimapHeight],
])
.on("brush", zoomBrushed)
.on("end", function(event) {
if (event.selection == null) {
resetZoom();
}
});
var selectBrush = d3
.brushX()
.extent([
[0, 0],
[width, height],
])
.on("brush", selectBrushed)
.on("end", function(event) {
if (event.selection == null) {
resetSelection();
}
});
function resetZoom() {
brushXScale.domain([
0,
d3.max(Object.keys(signalData), (d) => signalData[d].data.length),
]); // input
for (var signal in signalData) {
updateChart(signalData[signal].data, signalData[signal].name);
}
}
function zoomBrushed() {
var selectionPx = d3.brushSelection(this); // === [lower, upper] in pixels
// transform from pixels to x-values
var selectionX = [
xScale.invert(selectionPx[0]),
xScale.invert(selectionPx[1]),
];
// set x scale domain, then redraw the lines
brushXScale.domain(selectionX);
for (var signal in signalData) {
updateChart(signalData[signal].data, signalData[signal].name);
}
}
function resetSelection() {
selectBrush.on("end", null);
d3.selectAll("div[id^=signal] svg .brushcontainer").call(
selectBrush.clear
);
selectBrush.on("end", function(event) {
if (event.selection == null) {
resetSelection();
}
});
}
function selectBrushed() {
var selectionPx = d3.brushSelection(this); // === [lower, upper] in pixels
selectBrush.on("brush", null);
d3.selectAll("div[id^=signal] svg .brushcontainer").call(
selectBrush.move,
selectionPx
);
selectBrush.on("brush", selectBrushed);
}
//Generate the brush focus chart
generateMinimap(signalData.signal1.data);
//Generate charts dynamically as often as i have signals
for (var signal in this.signalData) {
generateChart(signalData[signal].data, signalData[signal].name);
}
// This function is for the one time preparations
function generateChart(data, name) {
var svg = d3
.select("#" + name)
.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 + ")"
);
//Gridlines
function Xgridlines() {
return d3.axisBottom(xScale).ticks(10);
}
svg
.append("g")
.attr("class", "grid")
.attr("transform", "translate(0," + height + ")")
.style("stroke-dasharray", "3,3")
.call(Xgridlines().tickSize(-height).tickFormat(""));
//clipPath to prevent path from overflow
svg
.append("defs")
.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
svg.append("g").attr("class", "brushcontainer").call(selectBrush);
svg
.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")");
svg.append("g").attr("class", "y axis");
svg
.append("path")
.attr("clip-path", "url(#clip)")
.attr("class", "line") // Assign a class for styling
.attr("fill", "none")
.attr("stroke", "blue");
updateChart(data, name);
}
// This function needs to be called to update the already prepared chart
function updateChart(data, name) {
var svg = d3.select("#" + name + " svg");
var yScale = d3
.scaleLinear()
.domain([0, d3.max(data)]) // input
.range([height, 0]); // output
function Ygridlines() {
return d3.axisLeft(yScale).ticks(5);
}
svg
.append("g")
.attr("class", "grid")
.attr("transform", "translate(0," - height - ")")
.style("stroke-dasharray", "3,3")
.call(
Ygridlines()
.tickSize(-width - margin.left)
.tickFormat("")
);
var line = d3
.line()
.x((d, i) => brushXScale(i))
.y((d) => yScale(d));
svg.select(".x.axis").call(d3.axisBottom(brushXScale));
svg.select(".y.axis").call(d3.axisLeft(yScale));
svg
.select(".line")
.datum(data) // 10. Binds data to the line
.attr("d", line); // 11. Calls the line generator
}
function generateMinimap(data) {
var yScale = d3
.scaleLinear()
.domain([0, d3.max(data)]) // input
.range([minimapHeight, 0]); // output
var line = d3
.line()
.x((d, i) => xScale(i))
.y((d) => yScale(d));
var svg = d3
.select("#minimap")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", minimapHeight + margin.top + margin.bottom)
.append("g")
.attr(
"transform",
"translate(" + margin.left + "," + margin.top + ")"
);
svg.append("g").call(zoomBrush);
svg
.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + minimapHeight + ")")
.call(d3.axisBottom(xScale)); // Create an axis component with d3.axisBottom
svg
.append("path")
.datum(data) // 10. Binds data to the line
.attr("class", "line") // Assign a class for styling
.attr("d", line) // 11. Calls the line generator
.attr("fill-opacity", "0.17")
.attr("fill", "blue")
.attr("stroke", "blue");
}
<div id="minimap"></div>
<!-- In the original project these divs are not
static and get generated with v-for as many times as
i have a signal in signalData -->
<div id="signal1"></div>
<div id="signal2"></div>
<div id="signal3"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
Upvotes: 0
Views: 213
Reputation: 13129
.append()
in updateChart
. You never remove the axis you draw, and just draw new ones over them...tickSizeInner()
:var signalData = {
signal1: {
name: "signal1",
data: [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 1, 2, 3, 5, 4, 1, 4, 2, 9, 7, 5, 7, 4, 6],
},
signal2: {
name: "signal2",
data: [6, 4, 8, 5, 4, 8, 4, 3, 5, 4, 5, 8, 7, 2, 9, 5, 4, 1, 2, 6, 0, 5, 7, 1],
},
signal3: {
name: "signal3",
data: [9, 5, 12, 3, 8, 4, 8, 6, 3, 4, 7, 8, 5, 2, 1, 8, 6, 8, 5, 8, 4, 8, 5, 1],
},
}
var margin = {
top: 10,
right: 50,
bottom: 40,
left: 50,
};
var width = window.innerWidth - margin.left - margin.right;
var height = 230 - margin.top - margin.bottom;
var minimapHeight = 150 - margin.top - margin.bottom;
var xScale = d3
.scaleLinear()
.domain([
0,
d3.max(Object.keys(signalData), (d) => signalData[d].data.length),
]) // input
.range([0, width]); // output
var brushXScale = d3
.scaleLinear()
.domain([
0,
d3.max(Object.keys(signalData), (d) => signalData[d].data.length),
]) // input
.range([0, width]); // output
var zoomBrush = d3
.brushX()
.extent([
[0, 0],
[width, minimapHeight],
])
.on("brush", zoomBrushed)
.on("end", function(event) {
if (event.selection == null) {
resetZoom();
}
});
var selectBrush = d3
.brushX()
.extent([
[0, 0],
[width, height],
])
.on("brush", selectBrushed)
.on("end", function(event) {
if (event.selection == null) {
resetSelection();
}
});
function resetZoom() {
brushXScale.domain([
0,
d3.max(Object.keys(signalData), (d) => signalData[d].data.length),
]); // input
for (var signal in signalData) {
updateChart(signalData[signal].data, signalData[signal].name);
}
}
function zoomBrushed() {
var selectionPx = d3.brushSelection(this); // === [lower, upper] in pixels
// transform from pixels to x-values
var selectionX = [
xScale.invert(selectionPx[0]),
xScale.invert(selectionPx[1]),
];
// set x scale domain, then redraw the lines
brushXScale.domain(selectionX);
for (var signal in signalData) {
updateChart(signalData[signal].data, signalData[signal].name);
}
}
function resetSelection() {
selectBrush.on("end", null);
d3.selectAll("div[id^=signal] svg .brushcontainer").call(
selectBrush.clear
);
selectBrush.on("end", function(event) {
if (event.selection == null) {
resetSelection();
}
});
}
function selectBrushed() {
var selectionPx = d3.brushSelection(this); // === [lower, upper] in pixels
selectBrush.on("brush", null);
d3.selectAll("div[id^=signal] svg .brushcontainer").call(
selectBrush.move,
selectionPx
);
selectBrush.on("brush", selectBrushed);
}
//Generate the brush focus chart
generateMinimap(signalData.signal1.data);
//Generate charts dynamically as often as i have signals
for (var signal in this.signalData) {
generateChart(signalData[signal].data, signalData[signal].name);
}
// This function is for the one time preparations
function generateChart(data, name) {
var svg = d3
.select("#" + name)
.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 + ")"
);
//clipPath to prevent path from overflow
svg
.append("defs")
.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
svg.append("g").attr("class", "brushcontainer").call(selectBrush);
svg
.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")");
svg.append("g").attr("class", "y axis");
svg
.append("path")
.attr("clip-path", "url(#clip)")
.attr("class", "line") // Assign a class for styling
.attr("fill", "none")
.attr("stroke", "blue");
updateChart(data, name);
}
// This function needs to be called to update the already prepared chart
function updateChart(data, name) {
var svg = d3.select("#" + name + " svg");
var yScale = d3
.scaleLinear()
.domain([0, d3.max(data)]) // input
.range([height, 0]); // output
var line = d3
.line()
.x((d, i) => brushXScale(i))
.y((d) => yScale(d));
svg.select(".x.axis").call(
d3.axisBottom(brushXScale)
.tickSizeOuter(0)
.tickSizeInner(-height)
);
svg.select(".y.axis").call(
d3.axisLeft(yScale)
.tickSizeInner(-width)
.tickSizeOuter(0)
.ticks(5)
);
svg
.select(".line")
.datum(data) // 10. Binds data to the line
.attr("d", line); // 11. Calls the line generator
}
function generateMinimap(data) {
var yScale = d3
.scaleLinear()
.domain([0, d3.max(data)]) // input
.range([minimapHeight, 0]); // output
var line = d3
.line()
.x((d, i) => xScale(i))
.y((d) => yScale(d));
var svg = d3
.select("#minimap")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", minimapHeight + margin.top + margin.bottom)
.append("g")
.attr(
"transform",
"translate(" + margin.left + "," + margin.top + ")"
);
svg.append("g").call(zoomBrush);
svg
.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + minimapHeight + ")")
.call(d3.axisBottom(xScale)); // Create an axis component with d3.axisBottom
svg
.append("path")
.datum(data) // 10. Binds data to the line
.attr("class", "line") // Assign a class for styling
.attr("d", line) // 11. Calls the line generator
.attr("fill-opacity", "0.17")
.attr("fill", "blue")
.attr("stroke", "blue");
}
.tick line {
stroke-dasharray: 3 3;
}
<div id="minimap"></div>
<!-- In the original project these divs are not
static and get generated with v-for as many times as
i have a signal in signalData -->
<div id="signal1"></div>
<div id="signal2"></div>
<div id="signal3"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
Upvotes: 2