draw empty arrow head and round bottom in d3 - svg

I'm a little new to SVG and d3 in general.
I'm trying to create a line with an "open arrow" on one end and a round circle on its other end.
By "open arrow" I mean a triangle like arrow without a base.
Here's an image for clarification:
https://www.google.com/url?sa=i&url=https%3A%2F%2Fwww.kindpng.com%2Fimgv%2FJJxmi_triangle-arrow-down-vector-arrow-down-white-png%2F&psig=AOvVaw0YWPto8OMDQIcx3DbPZlCI&ust=1642618347169000&source=images&cd=vfe&ved=0CAsQjRxqFwoTCMDt3q38u_UCFQAAAAAdAAAAABAE
I tried crating it but circle is not on the bottom edge and the arrow is not an open triangle.
This is what I managed to achieve.
let svg = d3.select("#graphContainer")
.append("svg");
let container = svg.append("svg").append("g");
svg.append("defs")
.append("svg:marker")
.attr("id", "arrowEnd")
.attr("refX", 6)
.attr("refY", 6)
.attr("markerWidth", 12)
.attr("markerHeight", 12)
.attr("orient", "auto")
.append("path")
.attr("d", "M2,2 L10,6 L2,10");
svg.select("defs")
.append("svg:marker")
.attr("id", "arrowStart")
.attr("refX", 0)
.attr("refY", 0)
.attr("markerWidth", 12)
.attr("markerHeight", 12)
.attr("orient", "auto")
.append("circle")
.attr("cx", "5")
.attr("cy", "2")
.attr("r", "5")
.attr("orient", "auto")
.style("stroke", "blue")
.style("fill", "none");
container.append("line")
.attr("x1", 150)
.attr("y1", 120)
.attr("x2", 150)
.attr("y2", 140)
.attr("stroke", "red")
.attr("stroke-width", 1)
.attr("marker-end", "url(#arrowEnd)")
.attr("marker-start", "url(#arrowStart)");
https://jsfiddle.net/eqawpkvt/
I'm not sure if marker is the right tag

Eventually I managed to create it.
Here's the fiddle:
https://jsfiddle.net/0v5gz1ft/
Here are the defs for the arrow start and end
svg.append("defs")
.append("svg:marker")
.attr("id", "arrowEnd")
.attr("refX", 6)
.attr("refY", 6)
.attr("markerWidth", 12)
.attr("markerHeight", 12)
.attr("orient", "auto")
.append("path")
.attr("d", "M-5,-3 L7,6 L-8,20")
.style("stroke", "#3278E0")
.style("fill", "none");
svg.select("defs")
.append("svg:marker")
.attr("id", "arrowStart")
.attr("refX", 6)
.attr("refY", 9)
.attr("markerWidth", 22)
.attr("markerHeight", 22)
.attr("orient", "auto")
.append("circle")
.attr("cx", "4")
.attr("cy", "9")
.attr("r", "3")
.attr("orient", "auto")
.style("stroke", "#3278E0")
.style("fill", "none");

Related

svg text wrapping issues

I am attempting to wrap called data into two lines of text on an svg. Right now it is displaying the text over six lines. Can anyone help with this.
function wrap(text, width, content) {
text.each(function () {
var text = d3.select(this),
words = content.split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1, // ems
x = text.attr("x"),
y = text.attr("y"),
dy = 0, //parseFloat(text.attr("dy")),
tspan = text.text(null)
.append("tspan")
.attr("x", x)
.attr("y", y)
.attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(''));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan")
.attr("x", x)
.attr("y", y)
.attr("dy", ++lineNumber * lineHeight + dy + "em")
.text(word);
}
}
});
}
Thermometer.prototype.drawTick = function(t, label, labelColor, textOffset, width, tubeWidth, lineColor, scale, svg) {
svg.append("line")
.attr("id", label + "Line")
.attr("x1", width / 2 - tubeWidth / 2)
.attr("x2", width / 2 + tubeWidth / 2)
.attr("y1", scale(t))
.attr("y2", scale(t))
.style("stroke", lineColor)
.style("stroke-width", "2px")
.style("shape-rendering", "crispEdges");
if (label) {
svg.append("text")
.attr("x", width / 2 + tubeWidth / 2 + 15)
.attr("y", scale(t))
.attr("dy", ".5em")
.text(label)
.style("fill", labelColor)
.style("stroke", "black")
.style("font-size", "14px")
.call(wrap,30,label)
}
};
return Thermometer;
the link to my fiddle is here
https://jsfiddle.net/corcorancr/sxs5n2cw/1/
The drawTick() function is calling another function called wrap() to plot the text. As might be suggested by that name, wrap() is splitting the input text into words and wrapping it onto a new line if it gets wider than the width you pass in.
The width value is the "30" in the following line:
.call(wrap,30,label)
Try changing it to something bigger so that it doesn't wrap so soon. 180 seems to be about the right value.
https://jsfiddle.net/sxs5n2cw/4/

SVG stretch with parent DIV

I am trying to make SVG responsive, so when window is re-sized, my svg will resize as well and fill parent div as it is when viewed first time.
Here is the fiddle - http://jsfiddle.net/nhe613kt/321/
HTML
<div id="myConta">
<div id="myContaList"></div>
</div>
JS
$(window).resize(OwResize);
function OwResize() {
$("#myConta").height(window.innerHeight - (window.innerHeight / 40));
}
var sideRectW = window.innerWidth / 20,
sideRectH = window.innerHeight / 20,
width = window.innerWidth - (window.innerWidth / 50),
height = window.innerHeight - (window.innerHeight / 40),
boxW = (width - sideRectW) / 4,
boxH = (height - sideRectH) / 4,
boxSize = boxW + boxH,
xPos1 = sideRectW,
xPos2 = boxW + sideRectW,
xPos3 = (boxW * 2) + sideRectW,
xPos4 = (boxW * 3) + sideRectW,
yPos1 = 0,
yPos2 = boxH,
yPos3 = boxH * 2,
yPos4 = boxH * 3;
var CreateRect = function (x, y, boxColor, boxId) {
svgContainer.append("rect")
.attr("x", x)
.attr("y", y)
.attr("id", "rectBox" + boxId)
.attr("width", boxW)
.attr("height", boxH)
.attr("fill", boxColor)
.attr("class", "hover_group")
.attr("preserveAspectRatio", "xMaxYMid meet")
.attr("viewBox", "0 0 " + $("#myConta").width() + $("#myConta").height())
.attr("onclick", "alert('haha');");
};
var CreateRectWithLength = function (x, y, w, h, boxColor) {
svgContainer.append("rect")
.attr("x", x)
.attr("y", y)
.attr("width", w)
.attr("height", h)
.attr("fill", boxColor);
};
var CreateText = function (x, y, text, textColor, size) {
svgContainer.append("text")
.attr("x", x)
.attr("y", y)
.attr("fill", textColor)
.attr("font-size", size)
.text(text);
};
var CreateText90 = function (x, y, text, textColor, size) {
svgContainer.append("text")
.attr("x", x)
.attr("y", y)
.attr("fill", textColor)
.attr("font-size", size)
.attr("transform", "rotate(-90," + x + 20 + ", " + y + ")")
.text(text);
};
var svgContainer = d3.select("#myConta")
.append("svg")
.attr("id", "myContasvg")
.attr("width", width)
.attr("height", height)
.attr("fill", "#2E2E2E")
.attr("float", "right")
.append("g");
CreateRectWithLength(0, 0, sideRectW, window.innerHeight, "Black");
CreateRectWithLength(0, height - sideRectH, width, sideRectH, "Black");
CreateText90(0, yPos3, "Sales", "white", 16);
CreateText(xPos3, height - sideRectH / 5, "Profit", "white", 16);
CreateText(sideRectW / 2, yPos1 + (boxH / 2), "3", "white", 12);
CreateText(sideRectW / 2, yPos2 + (boxH / 2), "2", "white", 12);
CreateText(sideRectW / 2, yPos3 + (boxH / 2), "1", "white", 12);
CreateText(sideRectW / 2, yPos4 + (boxH / 2), "0", "white", 12);
CreateText(xPos1 + (boxW / 2), height - sideRectH / 2, "0", "white", 12);
CreateText(xPos2 + (boxW / 2), height - sideRectH / 2, "1", "white", 12);
CreateText(xPos3 + (boxW / 2), height - sideRectH / 2, "2", "white", 12);
CreateText(xPos4 + (boxW / 2), height - sideRectH / 2, "3", "white", 12);
CreateRect(xPos1, yPos1, "#C0FC3E", 03);
CreateRect(xPos1, yPos2, "#60FC60", 02);
CreateRect(xPos1, yPos3, "#64FE2E", 01);
CreateRect(xPos1, yPos4, "#00FF00", 00);
CreateRect(xPos2, yPos1, "#F6FF33", 13);
CreateRect(xPos2, yPos2, "#AFFC3B", 12);
CreateRect(xPos2, yPos3, "#00FF00", 11);
CreateRect(xPos2, yPos4, "#64FE2E", 10);
CreateRect(xPos3, yPos1, "#FDB500", 23);
CreateRect(xPos3, yPos2, "#8DB723", 22);
CreateRect(xPos3, yPos3, "#AFFC3B", 21);
CreateRect(xPos3, yPos4, "#60FC60", 20);
CreateRect(xPos4, yPos1, "red", 33);
CreateRect(xPos4, yPos2, "#FDB500", 32);
CreateRect(xPos4, yPos3, "#F6FF33", 31);
CreateRect(xPos4, yPos4, "#C0FC3E", 30);
var rectContainer = d3.selectAll("#rectBox33");
var rectX = rectContainer.attr("x");
console.log(rectX);
Please Note
This is not exact what I am working on, but I tried to make it as close to working example as I could.
What I don't want
I want svg to resize and fill parent div automatically on window size.
I don't know if this is what you were after, but how is this?
Demo fiddle
You need to apply the viewBox and preserveAspectRatio attributes to your SVG. Also if you want the SVG to scale with its parent <div> then you should not set the width and height to fixed values. Instead leave them unset so that the default to the value "100%".

d3 axis label cut off in chrome and firefox

I've created a scatter plot in d3. The problem is that the y axis label does not appear in firefox and chrome (works fine in IE). I've tried doing things like making the svg width 100%, but for some reason the label always gets cut off.
<div id="TimeSeriesChartDiv" style="display: inline; float: right; width: 650px;
height: 415px">
</div>
//Width and height
var w = 600;
var h = 300;
var padding = 30;
var margin = { top: 20, right: 20, bottom: 30, left: 20 };
var df = d3.time.format("%b-%y");
//Create scale functions
var xScale = d3.time.scale()
.domain([d3.min(dataset, function (d) { return d[0]; }), d3.max(dataset, function (d) { return d[0]; })])
.range([padding, w - padding * 2])
.nice(5);
var yScale = d3.scale.linear()
.domain([0, d3.max(dataset, function (d) { return d[1]; })])
.range([h - padding, padding]);
//Define X axis
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(5)
.tickFormat(df);
//Define Y axis
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(5);
//Create SVG element
var svg = d3.select("#TimeSeriesChartDiv")
.append("svg")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom);
//Create X axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(20," + (h - padding) + ")")
.call(xAxis);
//Create Y axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + 50 + ",0)")
.call(yAxis);
svg.append("text")
.attr("class", "axislabel")
.attr("text-anchor", "end")
.attr("x", w / 2)
.attr("y", h + 8)
.text("Date");
svg.append("text")//-->>this is the text that gets cut off
.attr("class", "axislabel")
.attr("text-anchor", "end")
.attr("x", -100)
.attr("y", -15)
//.attr("dy", ".75em")
.attr("transform", "rotate(-90)")
.text(unit);
Any ideas would be much appreciated. Thanks
You are using negative coordinates for your text, which means they get drawn outside the SVG. It seems that IE9 doesn't seem to clip thing to the SVG area, other browsers do. The best solution is to add enough padding to your graph so that your text can be drawn inside the SVG. Disabling the clipping does not seem to be supported in all browsers.
Thanks Jan -- with additional help from:
http://www.d3noob.org/2012/12/adding-axis-labels-to-d3js-graph.html
this worked:
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("class", "axislabel")
.attr("text-anchor", "middle")
.attr("x", 0 - (h / 2))
.attr("y",0)//any negative value here wouldnt display in ff or chrome
.attr("dy", "1em")
.text(unit);

Appending axis to a D3 chart

I'm working through a book on data visualization with D3. I'm sorta new to this and I am trying to add axis to my chart. The example code works but for some reason when I try to append an axis class to my SVG element, it won't work.
My code is below:
function draw(data) {
//code
"use strict";
var margin = 50,
width = 700,
height = 300;
var x_extent = d3.extent(data, function(d){return d.collision_with_injury});
var y_extent = d3.extent(data, function(d){return d.dist_between_fail});
var x_scale = d3.scale.linear()
.range([margin, width-margin])
.domain(x_extent);
var y_scale = d3.scale.linear()
.range([height-margin, margin])
.domain(y_extent);
var x_axis = d3.svg.axis().scale("x_scale");
var y_axis = d3.svg.axis().scale("y_scale").orient("left");
d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d){return x_scale(d.collision_with_injury)})
.attr("cy", function(d){return y_scale(d.dist_between_fail)})
.attr("r", 5)
d3.select("svg")
.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (height-margin) + ")")
.call(x_axis);
d3.select("svg")
.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + margin +", 0)")
.call(y_axis)
d3.select('.y axis')
.append('text')
.text('mean distance between failure (miles)')
.attr('transform', "rotate (-90, -43, 0) translate(-280)")
d3.select('.x axis')
.append('text')
.text('collisions with injury (per million miles)')
.attr('x', function(){return (width / 2) - margin})
.attr('y', margin/1.5)
}
Class names cannot contain spaces. When you run the code .attr("class", "x axis") you're actually assigning the two classes x and axis to the element. This is not a problem as such, but the selector .y axis doesn't work as you would expect for the same reason. It tries to find an element with class y and an axis tag (as you didn't put a dot in front of it). This fails, as there is no such element.
The easiest way to fix this is probably to simply assign a one-word class, e.g. xAxis. Alternatively, you could change your selector to .y .axis to match elements that have those two classes.

Gradient smoothing in a long SVG object

I'm using D3 to create a large object filled with a gradient, but the larger the object, the gradient becomes less smooth. The following is an example of code that creates such type of artifacts:
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.js?1.27.1">
<script type="text/javascript">
var w = 4000,
h = 100,
m = 50;
var svg = d3.select("body").append("svg:svg")
.attr("width", w)
.attr("height", h);
var gradient = svg.append("svg:defs")
.append("svg:linearGradient")
.attr("id", "gradient")
.attr("x1", "0%")
.attr("y1", "0%")
.attr("x2", "100%")
.attr("y2", "0%")
.attr("spreadMethod", "pad");
for (i=0; i<m; i++) {
gradient.append("svg:stop")
.attr("offset", (i*100.0)/(m-1.0) + "%")
.attr("stop-color", "hsl(240,0%,"+(i%2)*100+"%)")
.attr("stop-opacity", 1);
}
svg.append("svg:rect")
.attr("width", w)
.attr("height", h)
.style("fill", "url(#gradient)");
</script>
Is it possible to increase the gradient smoothing with some SVG attribute?
This is a bug with Chrome's implementation of gradients, it happens with CSS gradients too. http://code.google.com/p/chromium/issues/detail?id=41756. It works fine in all browsers except Chrome.
Fortunately in your case there's a workaround: use spreadMethod: reflect; which will allow you to state the gradient in a smaller area and just let the browser repeat it:
var gradient = svg.append("svg:defs")
.append("svg:linearGradient")
.attr("id", "gradient")
.attr("x1", "0%")
.attr("y1", "0%")
.attr("x2", "2%")
.attr("y2", "0%")
.attr("spreadMethod", "reflect");
gradient.append("svg:stop")
.attr("offset", 0)
.attr("stop-color", "black")
.attr("stop-opacity", 1);
gradient.append("svg:stop")
.attr("offset", 1)
.attr("stop-color", "white")
.attr("stop-opacity", 1);
This is also has better performance. Hopefully your actual viz looks somewhat similar!
You can see a demo here:
http://jsfiddle.net/uKH4j/

Resources