I would like my line to draw like this example:
https://bl.ocks.org/shimizu/f7ef798894427a99efe5e173e003260d
The code below does not make any transitions, the chart just appears.
I'm aware of browser caching and that is not the issue. I've also tried changing the duration and that doesn't help either. I feel like I'm probably not being explicit about how I want d3 to transition, but I'm unsure how to give d3 what it wants. Your help is greatly appreciated.
EDIT: x-axis domain: [0, 1]. y-axis domain: [-18600, -3300].
// Here's just a few rows of the data
data = [{"threshold": 0.0, "loss": -18600},
{"threshold": 0.008571428571428572, "loss": -18600},
{"threshold": 0.017142857142857144, "loss": -18600}]
var svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 30, left: 20},
width = +svg.attr("width") - 400 - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var x = d3.scaleLinear()
.range([0, width]);
var y = d3.scaleLinear()
.range([0, height]);
var line = d3.line()
.x(d => x(d.threshold))
.y(d => y(d.loss));
var g = svg.append("g")
.attr("transform", "translate(" + (margin.left + 50) + "," + margin.top + ")");
d3.json("static/data/thresh_losses.json", function(thisData) {
draw(thisData);
});
let draw = function(data) {
$("svg").empty()
var x = d3.scaleLinear()
.range([0, width]);
var y = d3.scaleLinear()
.range([0, height]);
var line = d3.line()
.x(d => x(d.threshold))
.y(d => y(d.loss));
var g = svg.append("g")
.attr("transform", "translate(" + (margin.left + 50) + "," + margin.top + ")");
d3.selectAll("g").transition().duration(3000).ease(d3.easeLinear);
x.domain([0, d3.max(data, d => d.threshold)]);
y.domain([d3.max(data, d => d.loss), d3.min(data, d => d.loss)]);
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.append("text")
.attr("class", "axis-title")
.attr("y", 18)
.attr("dy", "1em")
.attr("x", (height/2) - 40)
.attr("dx", "1em")
.style("text-anchor", "start")
.attr("fill", "#5D6971")
.text("Threshold");
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(y))
.append("text")
.attr("class", "axis-title")
.attr("transform", "rotate(-90)")
.attr("y", -40)
.attr("dy", ".71em")
.attr("x", -height/2 + 40)
.attr("dx", ".71em")
.style("text-anchor", "end")
.attr("fill", "#5D6971")
.text("Profit ($)");
var line_stuff = g.selectAll(".line")
.data([data]);
line_stuff.enter().append("path").classed("line", true)
.merge(line_stuff);
g.selectAll(".line")
.transition()
.duration(10000)
.ease(d3.easeLinear)
.attr("d", line);
};
From the D3 documentation:
To apply a transition, select elements, call selection.transition, and then make the desired changes.
I found this in the code:
d3.selectAll("g").transition().duration(3000).ease(d3.easeLinear);
This won't animate anything, because there's no .attr() or .style() at the end—no "desired changes" are being made. It's a transition with no changes to make.
Now, let's look at this:
g.selectAll(".line")
.transition()
.duration(10000)
.ease(d3.easeLinear)
.attr("d", line);
This almost fulfills the requirements. It selects .line, creates the transition (and customizes it), and sets the d attribute. If you have d set elsewhere, then this would to transition the path from being empty to having all the data, only...
D3 doesn't transition strings that way. After first checking if the attribute is a number or color, D3 settles on using something called interpolateString. You'd think interpolateString would change characters from a to ab to abc, but actually, all it does is look for numbers within the string, and interpolate those, leaving the rest of the string constant. The upshot is, you just can't animate a string like d from empty to having data unless you do it yourself.
Here's how you can do that, using attrTween (note: not a good idea):
.attrTween("d", function() {
return function(t) {
const l = line(data);
return l.substring(0, Math.ceil(l.length * t));
};
})
This will actually transition between no text to the entire text of the d attribute. However, because of the way SVG paths work, this doesn't look very good.
There is another way, as demonstrated in the example you linked to (and also mentioned by Ryan Morton in a comment): transitioning the stroke-dashoffset. Here's how you would do that:
line_stuff.enter().append("path").classed("line", true)
.merge(line_stuff)
.attr('d', line)
.attr("fill", "none")
.attr("stroke", "black")
.attr("stroke-dasharray", function(d) {
return this.getTotalLength()
})
.attr("stroke-dashoffset", function(d) {
return this.getTotalLength()
});
g.selectAll(".line")
.transition()
.duration(10000)
.ease(d3.easeLinear)
.attr("stroke-dashoffset", 0);
Essentially, the first part tells D3 to:
create the line, make the fill invisible (so you can see the line)
make the stroke dashes equal to the total length of the line
offset the dashes, so that the line is completely hidden at the start
The next part sets up the transition and tells it to transition the offset to 0 (at which point the line will be completely visible because each dash is the same length as the line itself).
If you want to transition the fill, you could change .attr("fill", "none") to .attr("fill", "#fff"), and then do something like this:
g.selectAll(".line")
.transition()
.delay(10000)
.duration(2000)
.ease(d3.easeLinear)
.attr('fill', '#000');
This would use .delay() to wait for the first transition to finish before changing the background from white to black. Note that opacity might be better to animate for performance.
Related
I am building a dot plot histogram with d3.js v3 and I have pretty much finished everything up - except for whatever reason some of my data points are duplicating (certain circles repeating themselves - not all of them, just some). I tried tweaking the axis parameters, as well as the data itself [deleted rows with null values, etc]- however sadly to no avail.
Any help would be immensely appreciated.
Here's my relevant code:
<div id="dotHappy"></div>
var data = d3.csv('happy_dot_modified.csv', function(data) {
data.forEach(function(d) {
d["city"] = d["city"];
d["Happy"] = +d["Happy"];
d["thc"] = +d["thc"];
});
var margin = {
top: 30,
right: 20,
bottom: 30,
left: 50
},
width = 1560 - margin.left - margin.right,
height = 1260 - margin.top - margin.bottom;
I tried this coder block but it wasn't working. (Not sure if this is even what's giving me the issue anyways - perhaps not).
// var x = d3.scale.linear()
// .range([0, width]);
So I went with this:
var x = d3.scale.ordinal()
.rangePoints([0, width])
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var svg = d3.select("#dotHappy")
.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 chart = svg.append("g")
.attr("id", "chart");
Also tried tweaking this, which may or may not even be part of the problem.
x.domain(data.map(d => d.Happy));
y.domain([5, 33]);
// y.domain(data.map(d => d.city));
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
// .append("text")
.attr("class", "label")
.attr("x", width)
.attr("y", -6)
.style("text-anchor", "end")
.text("Happy");
svg.append("g")
.attr("class", "y axis")
// .attr("transform", "translate(0," + width + ")")
.call(yAxis)
// .append("text")
.attr("class", "label")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("THC");
var groups = svg.selectAll(".groups")
.data(data)
.enter()
.append("g")
.attr("transform", function(d) {
return "translate(" + x(d.Happy) + ".0)";
});
var dots = groups.selectAll("circle")
.data(function(d) {
return d3.range(1, +d.thc + 1)
// return d3.range(d.thc)
})
.enter().append("circle")
.transition().duration(1000)
.attr("class", "dot")
.attr("r", 10)
.attr("cy", function(d) {
return y(d)
})
.style("fill", "blue")
.style("opacity", 1);
})
Here is a snapshot of my csv file:
city. |. Happy. | thc
Boston. 37. 23
NYC. 22. 30
Chicago. 88. 5
Following is a screenshot of what it currently looks like. So in this case, the tooltip displaying the text box 'The Sister' should be only for one circle (because it should only be one data point), however if you hover over the other 10 orange circles below it, it's all the same - indicating it has been repeated 11 times total:
Actually, all of the circles are repeating vertically. You may not see them all because the repeated circles are being overlapped by other colored circles as these other circles get drawn. For example, the yellow data point "The Sister" is repeating all the way down to the bottom, but the data points below the yellow ones, in blue, pink, green, blue, etc., drew themselves on top of the yellow repeats.
The culprit is this code:
.selectAll("circle")
.data(function(d) {
return d3.range(1, +d.thc + 1)
// return d3.range(d.thc)
})
.enter().append("circle")
which, if you don't want it to repeat, should have been just one line:
.append("circle")
To explain what happened, this code:
var groups = svg.selectAll(".groups")
.data(data)
.enter()
.append("g")
.attr("class", "groups") //NOTE: you should add this line since you have 'selectAll(".groups")'
.attr("transform", function(d) {
return "translate(" + x(d.Happy) + ".0)";
});
already creates a g element for every row in the csv file. And for every g, you created an array using d3.range(1, +d.thc + 1), and appended a circle for each item in that array.
As an example, let's take the row representing "The Sister" data point that has a THC of 33. For that one data point, the code creates one <g>, inside of which it binds the array [1, 2, 3, ..., 33], and therefore appends 33 circles to the <g> element, with the cy attribute between y(1) and y(33).
Now, the question that follows is that, you specified a domain with a minimum of 5 with y.domain([5, 33]). Yet the data-bounded array, generated with d3.range, always begins with 1 and increments up to the value of THC. So some of the values in the array (1,2,3, and 4) always fall outside the y-axis, but d3 was able to translate it to a proper y-position. Is that possible? By default, yes, d3.scale extrapolates when the data is outside of the domain.
By default, clamping is disabled, such that if a value outside the input domain is passed to the scale, the scale may return a value outside the output range through linear extrapolation. For example, with the default domain and range of [0,1], an input value of 2 will return an output value of 2.
I'm new to d3 and currently trying to make a simple line chart using the example provided by Mike Bostock, I have arrieved to the following code.
<!DOCTYPE html>
<svg width="960" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 30, left: 50},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var parseTime = d3.timeParse("%d-%b-%y");
var x = d3.scaleTime()
.rangeRound([0, width]);
var y = d3.scaleLinear()
.rangeRound([height, 0]);
var line = d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.close); });
d3.tsv("data.tsv", function(d) {
d.date = parseTime(d.date);
d.close = +d.close;
return d;
}, function(error, data) {
if (error) throw error;
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain(d3.extent(data, function(d) { return d.close; }));
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.select(".domain");
g.append("g")
.call(d3.axisLeft(y))
.append("text")
.attr("fill", "#000")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.attr("text-anchor", "end")
.text("Weight (lbs)");
g.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", 1.5)
.attr("d", line)
.on("mouseover", handleMouseOver);
});
function handleMouseOver(d,i) {
console.log(d);
console.log(i);
}
</script>
taken from the following link, I append the link if you want to test with the sample data https://bl.ocks.org/mbostock/3883245
The thing is that I want to add a new feature where the user can hover over a part of the line and see what is the value of the data at that moment, what I understand is that I append a new path for each entry in the data, the problem is that when I add a callback to the mouseover event that is suppose to receive as a parameter the data being hover like this:
g.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", 1.5)
.attr("d", line)
.on("mouseover", handleMouseOver);
function handleMouseOver(d,i) {
console.log(d);
console.log(i);
}
The console.log(d) shows all the data in the data array and not the specific entry in the array that is being hovered, also the index i always gives 0. I want to know what I'm doing wrong or how can I achieve this. Thanks in advance.
Take the following code for the last append (all else is unchanged; and in that block, I also only changed the lines ending with //!!:
g.append("g").selectAll("path").data(data.slice(0, data.length-1)).enter().append("path") //!!
//.datum(data) //!!
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", 1.5)
.attr("d", function(d,i){return line([data[i], data[i+1]])}) //!!
.on("mouseover", handleMouseOver);
This gives you the correct data and index on mouse over depending on the segment.
Let me dive into the background a little bit:
datum sets the whole data as input for the only instance of path (when checking the DOM in your code above or bl.ocks.org, you'll only see one <path> with an insanely long d. Which is nice, as the line() function can handle this perfectly well. However, you only have ONE element for mouseover which doesn't help
hence, I chose another approach with one path for each line segment: My code has an insane number of <path>s with a very simple d each. However, each path can have a separate mouseover
to not get overwhelmed, I enclosed all the paths in a g, which doesn't hurt anyway
I did use the data() function. You can read up here for details: https://github.com/d3/d3-selection#selection_data
in brief, I tell it to take a selection of all path elements currently under g (none), and append as many new paths as necessary to satisfy the data at hand. Then, for each path, apply the next lines
(this doesn't yet update from a new data, but I want to keep it short)
and finally, to make it sound, I had to slice the data for each input
(I didn't use ES6 syntax for simplicity now, though ES6 would look nicer and is shorter. Doesn't matter for the result, however)
I had a D3js code which produces bar graphs and works fine with version 3.x. I wanted to upgrade the code to version 5 in the interest of being updated. Upon doing so I was able to correct a number of syntax updates such as scaleLinear, scaleBand, etc. The data is imported via tsv. The code is able to show the graph on the page with the correct x axis widths for the bars. However, the yAxis bars go out of bounds and the scale on the y-axis is very short. For example, the data shows the maximum value of the data to be 30000, but the yaxis is only from 0-90. Upon further investigation the d.RFU values from which the y data is generated seems to be not converted from string to integers. In the v3 code, I had a function at the end which converted the type of d.RFU to integer using the unary operator
d.RFU = +d.RFU
However, it seems to be not working in v5. Could this be due to the promises implementation in replacement of the asynchronous code?
Any solutions on how to fix this in version 5?
Please let me know if you need any more information and forgive me if I have missed out anything as I am new to programming and this website. Any help is appreciated.
Here is parts of the code which I have right now:
//set dimensions of SVG and margins
var margin = { top: 30, right: 100, bottom: 50, left: 100, },
width = divWidth - margin.left - margin.right,
height = 250 - margin.top - margin.bottom,
x = d3.scaleBand()
.range([0, width - 20], 0.1),
y = d3.scaleLinear()
.range([height,0]);
//setup the axis
var xAxis = d3.axisBottom(x);
var yAxis = d3.axisLeft(y);
var svg = d3.select("#bargraphID")
.append("svg")
.attr("width", width + margin.left + margin.right - 100)
.attr("height", height + margin.top + margin.bottom - 10)
.append("g")
.attr("transform", "translate (" + margin.left + "," + margin.top + ")");
d3.tsv(filename).then(function(data) {
// get x values from the document id
x.domain(data.map(function(d) {
return d.ID;
}));
yMax = d3.max(data, function(d) {
return d.RFU;
});
// get the y values from the document RFU tab
y.domain([0, yMax]);
//create the x-axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate (0, " + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "middle")
.attr("dx", "0em")
.attr("dy", "-0.55em")
.attr("y", 30)
.attr("class", "x-axisticks");
//create the y-axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
//add the data as bars
var bar = svg.selectAll("bar")
.data(data)
.enter()
.append("rect")
.style("fill", barColor)
.attr("fill-opacity", "0.3")
.attr("x", function(d) {
return x(d.ID);
})
.attr("width", x.bandwidth())
//set initial coords for bars for animation.
.attr("y", height)
.attr("height", 0)
//animate bars to final heights
.transition()
.duration(700)
.attr("y", function(d) {
return y(d.RFU);
})
.attr("height", function(d) {
return height - y(d.RFU);
})
.attr("fill-opacity", "1.0")
.attr("class", "y-data");
});
//convert RFU to integers
function type(d) {
d.RFU = +d.RFU;
return d;
}
Just like with the old v3 and v4 versions, you have to pass the row conversion function to d3.tsv in D3 v5:
d3.tsv(filename, type)
Then, use the promise with then. Have in mind that d3.tsv always return strings (be it D3 v3, v4 or v5), because:
If a row conversion function is not specified, field values are strings.
Here is the demo with fake data:
var tsv = URL.createObjectURL(new Blob([
`name RFU
foo 12
bar 42
baz 17`
]));
d3.tsv(tsv, type).then(function(data) {
console.log(data)
})
function type(d) {
d.RFU = +d.RFU;
return d;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
PS: Since SO snippet may have a problem loading that blob in some browsers, here is the same code in JSFiddle, check the console: https://jsfiddle.net/kv0ea0x2/
Everything seems to be working as expected except for the text element. I can't seem to get text elements to append to my g element. Here is my code so far. I've inspected the DOM in a chrome browser, but I don't see any text elements and I'm not sure why. I was using this site as a sort of guide: https://www.dashingd3js.com/svg-text-element.
Also, I know the elements should stack on each other since they all share the same x and y position, I'm just trying to get the elements to appear first.
var svg = d3.select('svg'),
margin = {top: 60, right: 20, bottom: 45, left: 60},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var x = d3.scaleBand().rangeRound([0, width]).padding(.1);
var y = d3.scaleLinear().range([height, 0]);
var g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
x.domain(test.map(function(d) { return d.level; }));
y.domain([0, d3.max(test, function(d) { return d.time; })]);
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.attr("writing-mode", "tb-rl")
.call(d3.axisBottom(x).tickSize(0).tickPadding(10));
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(y).ticks(10))
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 9)
.attr("dy", "0.71em")
.attr("text-anchor", "end")
.text("Frequency")
g.selectAll(".bar")
.data(test)
.enter()
.append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.level); })
.attr("y", function(d) { return y(d.time); })
.attr("width", x.bandwidth())
.attr("height", function(d) { return height - y(d.time); })
var text = g.selectAll("text")
.data(test).enter()
.append("text");
var textLabels = text.attr("x", 100)
.attr("y", 100)
.text("testing")
.attr("fill", "blue")
your selection g.selectAll("text") will include text from the axes you appended to the g element earlier in the code, so your "enter" won't have anything in it. D3 compares the incoming to data to the items in the selection, and if you don't specify a key, will do a simple comparison on the number of elements in each, and then add (enter) and remove (exit) accordingly.
If you change your selection to something that you know won't be on in DOM yet (ie an empty selection), for example g.selectAll(".label"), then when you append data, the enter selection will contain your new text labels.
I have an otherwise fine working grouped bar chart script to which I'm trying to add simple reference lines. The relevant code:
//Set up margins and dimensions according to http://bl.ocks.org/3019563
var margin = {top: 20, right: 10, bottom: 20, left: 30},
width = 810 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
/* Set up the primary x scale */
var x0 = d3.scale.ordinal()
.rangeRoundBands([0, width], .1)
.domain(data.map(function (d) {
return options.xPrimaryScaleAccessor(d);
}));
/* Set up the secondary x scale */
var x1 = d3.scale.ordinal()
.domain(xSecondaryScaleValues)
.rangeRoundBands([0, x0.rangeBand()]);
/* Set up the y scale as a linear (continous) scale with a total range of 0 - full height and a domain of 0-100 */
var y = d3.scale.linear()
.range([height, 0])
.domain([0, 100]);
/* Set up a color space of 20 colors */
var color = d3.scale.category20();
/* Set up the x axis using the primary x scale and align it to the bottom */
var xAxis = d3.svg.axis()
.scale(x0)
.orient("bottom");
/* Set up the y axis using the y scale and align it to the left */
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
/* Create an SVG element and append it to the body, set its dimensions, append a <g> element to
* it and apply a transform translating all coordinates according to the margins set up. */
var svg = d3.select(options.target).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 + ")");
//Create a space for definitions
var defs = svg.append("defs");
setupDropShadowFilter(defs, 3, 3, 3); //Sets up a gaussian blur filter with id 'drop-shadow'
/* Append a <g> element to the chart and turn it into a representation of the x axis */
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
/* Append a <g> element to the chart and turn it into a representation of the y axis */
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text(options.yLabel);
var dataArr = y.ticks(yAxis.ticks());
/* Draw the reference lines */
svg.selectAll("line")
.data(dataArr)
.enter().append("line")
.attr("x1", 0)
.attr("x2", width)
.attr("y1", y)
.attr("y2", y)
.style("stroke", "#ccc");
/* Set up the bar groups */
var group = svg.selectAll(".group")
.data(data)
.enter().append("g")
.attr("class", "g")
.attr("transform", function(d) { return "translate(" + x0(options.xPrimaryScaleAccessor(d)) + ",0)"; });
/* Draw the bars */
group.selectAll("rect")
.data(options.valueAccessor)
.enter().append("rect")
.attr("width", x1.rangeBand())
.attr("x", function(d) { return x1(d.label); })
.attr("y", function(d) { return y(d.value); })
.attr('rx', options.barCornerRadius)
.attr('ry', options.barCornerRadius)
.attr("height", function(d) { return height - y(d.value); })
.style("fill", function(d) { return getStripedPattern(defs, color(d.label)); //Sets up a pattern and returns its ID })//Todo: fill with pattern instead. see http://tributary.io/tributary/2929255
.style("filter", "url(#drop-shadow)");
/* Draw a legend */
var legend = svg.selectAll(".legend")
.data(xSecondaryScaleValues)
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + (xSecondaryScaleValues.length-i-.25) * (height/xSecondaryScaleValues.length) + ")"; });
legend.append("rect")
.attr("x", width - 9)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("y", 9)
//.attr("dy", ".35em")
.attr("transform", "translate(" + (width - 6) + ",-8)rotate(-90)" )
.style("text-anchor", "start")
.text(function(d) { return d; });
EDIT: I have also tried to append rect elements instead with hardcoded coordinates and dimensions, but those also didn't make it to the DOM.
EDIT 2: More or less full code now included.
Basically, nothing happens. No lines are appended and there are no errors in the console. The dataArr is a plain array of numbers and y(number) is confirmed to return good values in the output range.
I think (and debug suggests) that the chain dies at the append() stage, possibly because .enter() return something useless.
Console log after .data():
Console log after .enter():
Console log after .append():
I've been stuck on this for a good while now, so grateful for any ideas about what may go wrong. I'm sure I'm overlooking something obvious...
The problem is that the code that generates the axes appends line elements to the SVG. As it is run before appending the reference lines, calling svg.selectAll("line").data(...) matches the existing lines with the data. There are more lines than data elements, so no new elements need to be added and the .enter() selection is empty.
There are a few ways to fix this. You could move the code that generates the reference lines further up. You add a g element that contains these lines. You could have a special class for these lines and adjust the selector accordingly. Or you could provide a custom matching function to .data().