I'm working on building a range slider and I am stuck trying to use the .drag function. Specifically I can't seem to get any function to run during start or drag.
I have a very simple component here:
<g
className="handle1"
transform={`translate(${xScale(value)},0)`}
onMouseOver={() => onMouseOver()}
>
<rect
className="valueBuble"
x="-8"
y="-2.5rem"
width="1rem"
height="1rem"
fill="green"
></rect>
<text
x="0"
y="-1.75rem"
width="1rem"
height="1rem"
fill="white"
textAnchor="middle"
>
{value}
</text>
<circle r="10px" fill="#fa7070" />
</g>
The entire <g> moves according to a value prop. Now my d3 code with the drag function:
d3.select("g.handle1")
.call(d3.drag().on("start", drag).on("drag", drag));
Here I attempted to call a function named drag however this function is not being called. I'm testing this with a console.log:
const drag = (event, d) => {
console.log(event.target.value);
};
I am using React functional components. Any advice on how to fix the problem.
Code Pen example: https://codepen.io/trevordammon/pen/WNxWQRm
Related
I am a Javascript beginner trying to use local storage to store a set of button toggle states (on/off). My goal is to be able to toggle a set of svg fills, on and off, and have the states stored, returning to the selected state, after a browser refresh.
//LOOP 1 - GET STORED VALUES AND SET SVGs ACCORDINGLY
function grabStoredStates(){
for (let i=0; i < localStorage.length; i++) {
let key = localStorage.key(i);
let value = localStorage.getItem(key);
if (value == "on") {
//"cbox" below is from LOOP 2 - maybe it's causing the problem
cbox[i].style.fill = "coral";
}
}
}
//LOOP 2 - TOGGLE AND STORE TOGGLE STATES
let cbox = document.querySelectorAll(".toggleMe");
for (let i = 0; i < cbox.length; i++) {
cbox[i].addEventListener("click", function() {
if (cbox[i].style.fill == "coral") {
cbox[i].style.fill = "white";
localStorage.setItem(cbox[i].id, "off");
}else{
cbox[i].style.fill = "coral";
localStorage.setItem(cbox[i].id, "on");
}
});
}
<body onload="grabStoredStates()">
<svg height="300" width="700>
<g pointer-events="all">
<rect class="toggleMe" cursor:pointer; id="rect1" width="100" height="100" x="50" y="80" fill="aliceblue" stroke="black" stroke-width="2" />
<rect class="toggleMe" cursor:pointer; id="rect2" width="100" height="100" x="175" y="80" fill="aliceblue" stroke="black" stroke-width="2" />
<rect class="toggleMe" cursor:pointer; id="rect3" width="100" height="100" x="300" y="80" fill="aliceblue" stroke="black" stroke-width="2" />
<rect class="toggleMe" cursor:pointer; id="rect4" width="100" height="100" x="425" y="80" fill="aliceblue" stroke="black" stroke-width="2" />
</g>
</svg>
</body>
My problem is that if you load the page, and then open local storage in Chrome Dev Tools, (Application > Storage > Local Storage), and then click the rectangles in sequence from left to right you should see this, as they toggle correctly from light blue to orange:
Key Value
rect1 on
rect2 on
rect3 on
rect4 on
In local storage, the keys and values are, to that point, matched properly, showing all squares to be orange, as intended. However, if you then click rect3, (the third rectangle from the left), for instance, toggling it to light blue, with an expected corresponding "off" state -- on refreshing the page, the stored values appear to get jumbled, with the loop apparently renumbering the rectangles as if it was starting from [0] -- ignoring the intended key/value pairing. You then see this:
Key Value [PROBLEM]
rect1 on
rect2 on [ --> Displays as Off]
rect3 off [ --> Displays as on]
rect4 on
Is there any way that I can make the keys and values stay together, regardless of loop called. The "grabStoredStates()" loop appears to work, as tested by manually changing values and refreshing... I suspect, then, that it has to do with the transition between two separate for loops? Maybe the "cbox" loop should also be in a function? I looked at the prior stack overflow "can't use booleans in local storage" answer, but as I'm using "on" and "off" that doesn't seem to apply. I also saw a jQuery answer, but I'm not familiar with that library yet. (I wouldn't, however, turn down a jQuery answer if offered.)
Again, my original goal was to be able to toggle svg fills on and off and have the states stored after browser refresh. I'd love a more elegant way if there is one...
Thanks everyone, in advance, for your help.
Here I tried to make a simpler version. The presentation of the <rect>s relies on an attribute called data-state. When the document is loaded the attributes will be updated (I commented out the code for localStorage and replaced it with the array testArr). An event listener on <g> will handle click events and test if a <rect> was clicked, if so, the attribute is updated/toggled. After that the values are all saved to localStorage/testArr.
var cbox;
var testArr = ['on', 'off', 'off', 'on']; // for testing
document.addEventListener('DOMContentLoaded', e => {
cbox = document.querySelectorAll(".toggleMe");
/* // Code for localStorage:
Array.from(Array(localStorage.length).keys()).forEach(i => {
let key = localStorage.key(i);
let value = localStorage.getItem(key);
cbox[i].attributes['data-state'] = value;
});*/
testArr.forEach((state, i) => cbox[i].attributes['data-state'].value = state); // for testing
document.querySelector('g').addEventListener('click', e => {
if(e.target.classList.contains('toggleMe')){
let currentValue = e.target.attributes['data-state'].value;
e.target.attributes['data-state'].value = (currentValue == 'on') ? 'off' : 'on';
/* // Code for localStorage:
[...cbox].forEach((elm, i) => {
let value = elm.attributes['data-state'].value;
localStorage.setItem(`Rect${i}`, value);
});*/
testArr = [...cbox].map(elm => elm.attributes['data-state'].value); // for testing
console.log(testArr.join(',')); // for testing
}
});
});
rect.toggleMe {
cursor: pointer;
width: 50px;
height: 50px;
stroke: black;
stroke-width: 2px;
}
rect[data-state="on"] {
fill: aliceblue;
}
rect[data-state="off"] {
fill: coral;
}
<body>
<svg height="300" width="300">
<g>
<rect class="toggleMe" data-state="off" x="50" y="80" />
<rect class="toggleMe" data-state="off" x="110" y="80" />
<rect class="toggleMe" data-state="off" x="170" y="80" />
<rect class="toggleMe" data-state="off" x="230" y="80" />
</g>
</svg>
</body>
I want to dynamically draw/turn an SVG with React from degree paramters that im recieving. Its like the direction arrow from a weather-info.
How do I dynamically draw an SVG in Javascript / ReactJS?
How do I turn the SVG with variable input?
I'm aware, that you can draw SVG-Graphics like this:
function draw_square() {
var draw = SVG('square_1');
draw.size(120, 120);
var square = draw.rect(100, 100);
square.attr({ fill: '#f06' });
}
draw_square()
but how do I connect different svg-paths to each other?
I recommend a slightly different approach:
const WindDirectionIcon = ({ width, height }) => {
return (
<svg
id="wind-direction-arrow"
width={width}
height={height}
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M6 0L0 12L6 9L12 12L6 0Z" fill="black" />
</svg>
);
};
export default function App() {
useEffect(() => {
const degrees = 90; // Retrieve from your API
const arrow = document.getElementById("wind-direction-arrow");
arrow.setAttribute("transform", `rotate(${degrees})`);
}, []);
return (
<div className="App">
<WindDirectionIcon width="24" height="24" />
</div>
);
}
So the svg itself can be composed of a single path and the only thing we do with javascript is to set the transform attribute to rotate a certain number of degrees based on the API data you receive using template literals.
Rotate with javascript based on this answer.
Sandbox Example
Doughnut picture
For example, given image like above, what I want to do is draw the exact same shaped polyline object on SVG(Im creating a 'drawing' or should I say 'brush' tool based on SVG and that is why Im using polyline so that user can paint with his mouse or can even use eraser with his or hers mouse). And following is how I would achieve this.
draw the given image on canvas context.
get all the coordinates of pixel that is colored #000000.
with that list of coordinates create a Polyline on SVG.
and by this process I get this as a result doughnut drawin with svg polyline(now this is just an example result that it is formed ugly because I had to draw it manually with my hand. But my purpose is to get same shaped with an input image)
But I'm not sure if this is the only way or even not sure if I should stick with SVG. Are there any other good ways to achieve this? or would using canvas instead of SVG make it easier?
This shape can be drawn with circles.
Cutouts made using a mask composed of circles
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="405" height="401" viewBox="0 0 405 401" >
<defs>
<mask id="msk1" >
<rect width="100%" height="100%" fill="white" />
<g fill="black">
<circle cx="202" cy="200" r="40" />
<circle cx="260" cy="298" r="40" />
<circle cx="215" cy="303" r="20" />
</g>
</mask>
</defs>
<circle cx="202" cy="200" r="98" fill="black" mask="url(#msk1)" />
This is supposing that you already have an SVG path.
In order to draw a polygon you will need to split your path by the M commands. In the next example I did it manually but you can do it dynamically. This is important because otherwise you'll get a split in the polygon.
You will also need to set a precision, meaning the distance between the points of the polygon.
Please read the comments in my code.
let paths = document.querySelectorAll("#theGroup path");
let precision = 5;//how far one of other the points of the polygon are
let points = [];// the array of points
// for each path in the array of paths
paths.forEach(p=>{
// get the total length
let totalLength = p.getTotalLength();
// get the number of points for the polygon in base of the precision
let numPoints = ~~(totalLength / precision);
// calculate the segment length
let segmentLength = totalLength / numPoints;
for(let i = 0; i <= numPoints; i++ ){
let point = p.getPointAtLength(i*segmentLength);
// get the coordinates of this point and push it
points.push(point.x);
points.push(point.y);
}
})
//set the value for the points attribute of the polygon
poly.setAttributeNS(null,"points", points.join())
svg{border:1px solid; width:90vh;}
path{fill:none}
<svg viewBox="0 0 531 531">
<g id="theGroup">
<path id="donut" d="M268.64,76.066c70.065,2.632,125.154,32.347,163.73,91.372
c14.944,22.864,23.47,48.161,27.698,75.22c3.987,25.512,2.188,50.551-3.64,75.354c-4.821,20.522-13.383,39.648-24.866,57.406
c-2.003,3.099-3.899,3.396-7.365,1.548c-30.011-16.005-64.509-10.767-87.731,14.13c-6.295,6.748-9.985,15.893-15.108,23.783
c-1.548,2.384-3.508,5.256-5.938,6.189c-19.202,7.375-32.874,20.547-41.279,39.064c-1.911,4.211-4.254,5.562-8.308,5.085
c-13.198-1.554-26.507-2.515-39.562-4.873c-30.46-5.502-57.275-19.262-81.055-38.724c-28.703-23.491-49.496-52.646-61.424-88.046
c-7.479-22.198-11.34-44.892-10.42-68.225c2.042-51.761,20.944-96.305,57.854-133.023c22.272-22.156,48.427-37.859,78.3-47.183
C228.671,79.17,248.365,75.884,268.64,76.066z"/>
<path id="hole" d="M340.466,271.259c0.179-40.212-32.175-73.14-72.067-73.348
c-40.072-0.208-73.264,32.326-73.485,72.032c-0.226,40.441,32.218,73.372,72.436,73.522
C307.646,343.616,340.286,311.382,340.466,271.259z"/>
</g>
<polygon id="poly" fill="gold" points = "" />
</svg>
I have put together some SVG circles, with the colour of the circles supposed to alternate on each click between blue and red.
I'm getting some inconsistent behaviour from this, as when I click on the circles in my localhost environment, it works, but the colour doesn't change to red until the second click.
When I put the code into jsFiddle, one of the circles works, one doesn't, even though the code is the same, and they both give the same error in the console
[Error] ReferenceError: Can't find variable: changeColor1
onclick (_display:79)
var shapeClick = document.getElementById("circle0").addEventListener("click", changeColor);
var clicks = 0;
var colorToggle = true;
function changeColor() {
var color = colorToggle ? "#ff0000" : "#1dacf9";
circle0.setAttribute('fill', color);
colorToggle = !colorToggle;
}
var shapeClick = document.getElementById("circle1").addEventListener("click", changeColor1);
var clicks = 0;
var colorToggle = true;
function changeColor1() {
var color = colorToggle ? "#ff0000" : "#1dacf9";
circle1.setAttribute('fill', color);
colorToggle = !colorToggle;
}
<svg id="table1" width="66%" height="100vh" viewBox="0 0 700 666">
<circle id="circle0" class="circles" cx="170" cy="125" r="20" fill="#1dacf9" stroke="black" stroke-width="2" onclick="changeColor()"/>
<circle id="circle1" class="circles" cx="250" cy="45" r="20" fill="#1dacf9" stroke="black" stroke-width="2" onclick="changeColor1()"/>
</svg>
I'm sure there is a more streamline way to do this, especially if I scale it up and add 100 circles, I can't think of a way to do it without using a massive amount of code.
In your case, you are redefining variables that already exist, and both circles are referencing the same value for their toggle.
var shapeClick = ...
...
var shapeClick = ...
Also, you don't need to do onclick="function()" and addEventListener; they do the same thing.
As far as streamlining, there are a couple things you should do:
Create an object to hold toggle states. Keys should be IDs of the circles, and the values will be true or false to signify each circle's state.
Make one handler function, and use event.target.getAttribute('id') to identify which circle was clicked.
Should end up looking something like this:
var state = {};
var circleClickHandler = function (event) {
var id = event.target.getAttribute('id');
var color = state[id] ? "#ff0000" : "#1dacf9";
event.target.setAttribute('fill', color);
state[id] = !state[id];
};
// loop over each circle on the DOM
document.querySelectorAll('circle').forEach(function (element) {
element.addEventListener('click', circleClickHandler);
});
you are missing you opening svg tag
https://jsfiddle.net/9oagrcfu/
<svg>
<circle id="circle0" class="circles" cx="170" cy="125" r="20" fill="#1dacf9" stroke="black" stroke-width="2" onclick="changeColor()"/>
<circle id="circle1" class="circles" cx="250" cy="45" r="20" fill="#1dacf9" stroke="black" stroke-width="2" onclick="changeColor1()"/>
</svg>
I'm creating several click-able maps - and have been unsuccessful in trying to find how to access a tag for a path so that I can make all the different counties in the map clickable. (the SVG file is incredibly complicated... this is the structure and I can't change it - it's getting automatically generated for all the different states):
<svg width="932.25827pt" height="569.25354pt" viewBox="0 0 932.25827 569.25354" enable-background="new 0 0 932.25827 569.25354"
version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" >
<g id="Layers">
<g id="Ohio_Counties">
<clipPath id="SVG_CP_1">
<path d="M0,569.25354L0,0L932.25827,0L932.25827,569.25354L0,569.25354z"/>
</clipPath>
<path clip-path="url(#SVG_CP_1)" fill="none" stroke="#4E4E4E" stroke-width="0.48003" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round" d="
M374.18483,441.14215L375.38491,440.90214L375.62493,440.90214L376.58499,440.66213L376.82501,440.66213L377.54505,440.42212
L378.02508,440.42212L378.2651,440.42212L378.74513,440.42212L379.46518,440.66213L381.14529,440.66213L381.38531,440.66213L
381.62532,440.66213L382.10536,440.66213L382.8254,440.66213L384.50551,440.90214L384.74553,440.90214L386.18563,440.90214L386.42564,441.38217
L386.42564,441.62218L386.42564,441.86219L386.66566,441.86219L386.90567,441.86219L386.90567,442.1022L386.90567,442.34221L"/>
</path>
</g>
</g>
/* There are about 87 more paths in the file... this is just a part of one.*/
I'm using jquery.svg - but haven't found the solution in the documentation.
Here's the javascript:
function changeState(newState){
nS = newState.replace(/\s/g, '');
try {
var map = 'mapLayers/AEP'+nS+'Counties.svg';
$("#contentCounties").empty();
var currentCountyMap = $('#contentCounties').svg({loadURL: loc, onLoad: addClickEvents(this)});
} catch(err){
alert("the map " + currentCountyMap + " does not exist");
}
}
function addClickEvents(){
// this is where I want to put the code to
// add click events to the individual paths.
// Whether it's running a loop or using a jquery selector
// I'm fine with either.
// This is, in theory, what I think I want - but alas, it is wrong:
var svg=document.getElementByTagName("path");
alert(svg);
}
Looks like you've just left the s out of getElementsByTagName