This question already has answers here:
Create SVG tag with JavaScript
(2 answers)
Closed 5 years ago.
Hello I'm creating charts with Plotly and want my area-charts to have a gradient instead of the standard fill with opacity.
I build my graph using:
Plotly.newPlot(className, chartData, layout, {displayModeBar: showModeBar});
When the chart is build the dom surrounding the chart looks like this:
Then after that I create the gradient section using this code:
function createGradient() {
var svg = document.getElementById('line-chart-impressions').getElementsByClassName('trace')[0];
var fillChange = document.getElementById('line-chart-impressions').getElementsByClassName('js-tozero')[0];
var defs = document.createElement('defs');
var linearGradient = document.createElement('linearGradient');
linearGradient.setAttribute('id', 'gradient');
var stop1 = document.createElement('stop');
stop1.setAttribute("offset", "5%");
stop1.setAttribute("stop-color", "#FFC338");
var stop2 = document.createElement('stop');
stop2.setAttribute("offset", "100%");
stop2.setAttribute("stop-color", "#FFEA68");
svg.appendChild(defs);
defs.appendChild(linearGradient);
linearGradient.appendChild(stop1);
linearGradient.appendChild(stop2);
fillChange.setAttribute('fill', 'url(#gradient)');
}
After that the dom looks like:
I suspect that this is because I append the gradient later but I really want to know if there is a solution. My chart has no fill like this.
Your gradient definition is missing positioning information. From where to where should the gradient change? For example, to define a gradient that lets the color change from the bottom to top of your chart area:
<linearGradient id="gradient" gradientUnits="objectBoundingBox"
x1="0" x2="0" y1="1" y2="0">
<stop offset="5%" stop-color="#FFC338" />
<stop offset="100%" stop-color="#FFEA68" />
</linearGradient>
x1, y1, x2, y2 define the start and end of a line along which to gradually change the color. Each point in the area to be colored will get the color of the point on that line that it is nearest to.
You can express coordinates in relation to the bounding box of the area (which is the default, so gradientUnits="objectBoundingBox" can be left off), or in userspace coordinates with gradientUnits="userSpaceOnUse". Read up on further tweaks here.
From your screenshots it seems that you disabled the style attribute with the developer tools. Please note that the presentation attribute fill="url(#gradient)" has a lower specificity than a style="fill:rgb(255, 255, 255);". You should use
fillChange.style.fill = "url(#gradient)"
to make sure the style gets applied.
The screenshot shows an element lineargradient (note lowercase letters). It must be linearGradient, as the script you posted defines. Check what you really executed.
Related
I have a stacked barchart on codepen
If you notice the x-axis of the chart, it only is available for half of the barchart. Any change i do, the line does not extend till the end of the barchart.
screenshot
This is the code which affects the x-axis line of the graph
<g class="grid y-grid" id="yGrid">
<line x1="90" x2="1505" y1="490" y2="490"></line>
</g>
I tried changing the x2 value to a higher value but no effect.
There is one more thing i am trying to achieve. Its the x coordinates and y-coordinates for the entire graph as we have in below example.
Can someone please shed some light on how to get this on the graph
I have a an SVG where I am targeting a certain path element with id: _116.
I can fill the path easily with a solid colour by doing this:
d3.select("path#_116").style("fill", 'blue');
However lets say I have a linear scale colour so that I want a gradient applied to it:
let scale = d3.scaleLinear()
.domain([1,10])
.range(["white", "blue"]);
d3.select("path#_116").style("fill", scale);
This just doesn't work. For some reason it's setting the fill to a black colour (#212a39) which seems like is coming from the sibling element as shown below:
Any ideas on what's happening here?
The scale function will return a single color for a provided value (as is true for all D3 scaling functions), which likely explains the output you are seeing. The scale by default is not clamped, so the scale will interpolate values outside the domain and return values outside the range, which explains why you have a value that doesn't appear to fit in your domain.
Scaling functions do not modify the DOM but a gradient requires elements within the SVG to represent the gradients. Usually gradients are children of a defs tag:
<svg>
<defs>
<linearGradient id="gradient">
<stop stop-color="white" offset="0%"/>
<stop stop-color="blue" offset="100%"/>
</linearGradient>
</defs>
<rect width=300 height=100 fill="url(#gradient)"></rect>
</svg>
If you have an existing SVG and want to amend a single path, we can append the defs, the gradient, the stops, and apply it to a given path:
d3.xml("https://upload.wikimedia.org/wikipedia/commons/a/ab/Isobates-simple_example.svg")
.then(function(image) {
let svg = d3.select("body")
.append(()=>image.documentElement);
let gradient = svg.append("defs")
.append("linearGradient")
.attr("id","gradient");
gradient.append("stop")
.attr("stop-color","white")
.attr("offset","0%")
gradient.append("stop")
.attr("stop-color","blue")
.attr("offset","100%");
svg.selectAll("#path3156")
.style("fill", "url(#gradient)");
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.1.0/d3.min.js"></script>
There are ways you can more efficiently do this, though generally you'll be going about achieving the same structure in the DOM (using .append() and .html() can save some lines). And D3 is completely optional as well - this is just adding a few elements to the DOM and setting an existing element's fill.
Your question appears to be concerned with a singular gradient, but if we have many gradients the answer gets a bit more complicated if we want the gradients to be data driven and incorporate scaling into the mix. Since I don't think you are asking about that I'll leave that for another time.
I have an SVG-based graph. On top is a scrollable area, where the user can navigate around the graph, as shown in the image:
By dragging either of the tabs on the left/right, the user can zoom in on a particular part of the plot, as shown in the image:
Notice that only the selected portion of the graph is blue, and has a grey highlight. Both the blue and the highlight are unchanged, but are clipped using a clipping mask:
<clippath id="clip_path_scroll">
<rect id="clip_path_scroll_rect" x="x" y="y" width="w" height="h" stroke-width="0" />
</clippath>
and
<g clip-path="url(#clip_path_scroll)">
<rect id="highlighted" width="w" height="h" x="x" y="y" fill="rgba(0, 0, 0, .03)" stroke="none" />
<path id="blue_plot" d="..." stroke="rgba(26, 171, 219, 1)" stroke-width="2" fill="none"/>
</g>
And this all works fine and dandy. The clip path works flawlessly, when dragging the tabs. In that situation, it is changed incrementally, as the tab(s) slide.
But I have a reset button, that calls the following:
$('#left_scroll_group').attr('transform', 'translate(0, 0)');
$('#right_scroll_group').attr('transform', 'translate(0, 0)');
$('#clip_path_scroll_rect').attr('width', width).attr('x', x);
and when this runs, the outcome is the following:
Notice that the tab groups snap back to exactly where they should be, but it appears the clip path has only partially gone back to its original width and x-position.
BUT THIS ISN'T THE CASE
If I view the page source, and hover over the clippath rect object, it shows the following:
That is to say, it shows the rect object DID resize and move to its original position, but is not displaying properly. Moving either of the tabs immediately snaps the clippath back to working properly, as in the first two images.
Why is this happening? It happens whether the right tab is moved, or the left, or both. If the tabs are moved only a little, there doesn't seem to be much issue, but the obvious glitch/error increases as the tabs are moved further.
A similar function performs the same attr changes to the clippath, but it always shortens the path length. This glitch only seems to be happening when lengthening the length.
I am using the same types of operations on many other elements in the graph, and all work fine. All are referenced with jQuery notation (i.e. $('#element')).
I am not interested in using d3 or other libraries. I am looking for jQuery or pure JS solutions only.
EDIT 1
I've also tried
var steps = Math.floor(scrollPlotWidth - $('#clip_path_scroll_rect').attr('width'));
var curWidth = Math.ceil($('#clip_path_scroll_rect').attr('width'));
var curX = $('#clip_path_scroll_rect').attr('x');
for(var i = 0; i < steps; i++){
$('#clip_path_scroll_rect').attr('x', curX).attr('width', curWidth);
curX--;
curWidth++;
}
With no success. The changes are happening incrementally, but still no dice - it looks the same as before. This clipping issue is only happening in Safari, and seems to be fine in FireFox. Haven't tested others.
WORKING SOLUTION (HACKED)
When defining the rect within the clippath, I changed the width property. I've changed it from what is shown above to the following:
<rect id="highlighted" style="width:w;" height="h" x="x" y="y" fill="rgba(0, 0, 0, .03)" stroke="none" />
And then, the clippath functionality works properly IFF I apply the two operations:
$('#clip_path_scroll_rect').attr('x', x).css('width', w);
$('#clip_path_scroll_rect').animate(
{"width": w},
{duration: 1,
step: function(){
this.setAttribute("x", x);
}
});
Removing either of these will prevent it from working. I'm not sure why, but it will have to be a temporary fix until something better comes along. Answers are still very much welcome!
I have some circles made in d3. I need to add more circles when these circles are clicked and make the existing circles disappear(they should not be removed as I will use them again).
The way I'm doing this right now is through a listener to a click event on the original circles. (these are created with .selectAll('circle.nodes')
.on("click",function(d){
//do stuff}).duration(1000);
populateSubCircles);
I want the sub circles to appear around a center (I looked at my calculations and they seem to be correct).
var populateSubCircles = function(){
var subCircles = nodesG.selectAll("circles.subNodes").data(....
This correctly adds the secondary circles, and cx and cy seem to be correct(not too far off from the center circle). However, they don't seem to appear on the page(they appear at the top left at 0,0). Why is that happening? How do I fix that?
Thanks.
EDIT-
A picture or two may help.
It's likely that you don't have the radius set on the circles (it must be set explicitly on the circle element, and cannot be set via CSS, as only style properties such as fill, stroke, etc., can be set) .
As you can see, one svg circle shows normally when set with a radius (r) of 25, and the other does not, and its size is correctly reported as 0px x 0px. (The tooltip reports size, not position). Here's the SVG I used:
<svg>
<circle cx="100" cy="100" fill="#ffdf00"
stroke="#222222" stroke-width="2px" />
<circle cx="50" cy="50" fill="#ffdf00" r="25"
stroke="#222222" stroke-width="2px" />
</svg>
This image shows the second of two circles, using inspector in Chrome web tools, it correctly reports the size:
Again, using inspector in Chrome, highlighted the first SVG element where no radius was set:
It shows the size as 0px x 0px and shows the circle as if it were in the upper left corner of the SVG document.
http://jsfiddle.net/wiredprairie/gNrZ3/
So I have a single .svg file with a few pre-made gradient effects like this:
<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="1052.4" width="744.09" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" viewBox="0 0 670 680" style="width: 100%; height: 100%;">
<defs>
<radialGradient id="grad1" cx="50%" cy="50%" r="50%" fx="50%" fy="50%">
<stop offset="0%" style="stop-color: #a0a0a0;" />
<stop offset="100%" style="stop-color: #a0a0a0;" />
</radialGradient>
</defs>
<g>
<path id="path1" d="m124 263.35c6.4216-12.385 18.974-0.67157 0.72621z" fill="url(#grad1)">
</path>
</g>
</svg>
This is a simplified version of my svg file. It represents a map in which each state is a path element. Each state also has a radialgradient tag associated with it. The problem I'm having is that I include this SVG file twice in my HTML document and I alter the radial gradient tags on the svg to alter the color of the state on each map separately.
The maps also have some interactivity in them, I use the following code to load the svg and to add events that bring a state to the front (so its stroke is visible) when the user hovers the mouse over the map:
$divSVG.load("map.svg", function() {
$svg= $(this).find("svg");
$svg.find("path").each(function() {
$(this).bind("mouseenter", function() {
var $path= $(this);
var $parent= $path.parent();
//its necessary to detach and reattach the element so it comes to the front
//of the image (there is no z-index in SVG)
$path.detach();
$parent.append($path);
$path
.css("stroke", "#FF0000")
.css("stroke-width", "5px");
});
$(this).bind("mouseleave", function() {
$(this)
.css("stroke", "#FFFFFF")
.css("stroke-width", "3px");
});
}
});
Basically I just remove the element and reattach it to its parent when the element is hovered.
The problem: when the element is reattached on the map that was added first in the document it starts using the radial gradient present on the second map. That means that when I hover the first map the states changes colors to the same colors they have on the second map.
I believe the cause of this is that since its the same file loaded twice the gradient ids are conflicting causing the reattached element to take its color from the last gradient tag found in the HTML document instead of the gradient tag present in its own SVG tag.
So here is my problem, how do I solve it? I don't want to create a new SVG file for each map with unique ids. Nor do I want to manipulate the ids by javascript. Any ideas?
SVG injectors like Iconic SVGInjector or the newer Iconfu SVGInject handle this issue by making the IDs unique when inserting the SVG into the HTML document. The Iconic injector adds a running number to the ID (grad1 becomes grad1-1), while the Iconfu injector adds a short random string (grad1 becomes something like grad1-4ew8ZeSw).
Making IDs unique is not only necessary if two SVGs use the same ID, but also if the same SVG is used multiple times in an HTML document.