D3
OG
Old school D3 from simpler times
All examples
By author
By category
About
kenpenn
Full window
Github gist
styling SVG markers
<!doctype html> <html> <head> <meta charset="utf-8"> <title>Styling SVG Markers</title> <style id="styles"> html { background: hsl(0, 0%, 0%); -webkit-background-size: cover; -moz-background-size: cover; background-size: cover; } .wrap { max-width: 860px; margin: auto; overflow: auto; } .svg-container { width: 420px; padding: 15px; float: left; } .color-path { fill: none; stroke-width: 2.5px; } .layout-path { fill: none; stroke: none; } .marker { stroke: none; } .btn-light { cursor: pointer; fill: url(#btn-light); stroke: hsl(0, 0%, 60%); stroke-width: .5px; } .btn-light-txt { font-family: "Lucida Grande", "Lucida Sans Unicode", Helvetica, Arial, Verdana, sans-serif; text-anchor: middle; fill: hsl(0, 0%, 30%); stroke: none; font-size: 1rem; -webkit-user-select: none; pointer-events: none; } .splainin { font-family: "Lucida Grande", "Lucida Sans Unicode", Helvetica, Arial, Verdana, sans-serif; background : white; border-radius: 4px; color: hsl(0, 0%, 30%); float: left; font-size: .85rem; width: 380px; padding: 10px 12px; margin: 40px auto; } .splainin a { color: hsl(203, 100%, 50%); text-decoration: none; font-weight: 600; } .splainin ul { margin: 0; padding-left: 8px; list-style: none; } .pi { color: hsl(332, 98%, 51%); } @media only screen and (max-width : 870px) { .svg-container { float: none; display: block; margin: auto; } .splainin { float: none; display: block; } } </style> </head> <body> <div class="wrap"> <div class="svg-container"> <svg xmlns="https://www.w3.org/2000/svg" xmlns:xlink="https://www.w3.org/1999/xlink" xmlns:xml="https://www.w3.org/XML/1998/namespace" xml:space="preserve" x="0" y="0" width="400px" height="440px" viewBox="0 0 500 550" > <defs> <marker id="marker-circle" markerWidth="10" markerHeight="10" refx="5" refy="5"> <circle cx="5" cy="5" r="3" class="marker"/> </marker> <marker id="marker-square" markerWidth="7" markerHeight="7" refx="4" refy="4"orient="auto"> <rect x="1" y="1" width="5" height="5" class="marker"/> </marker> <marker id="marker-arrow" markerWidth="12" markerHeight="12" refx="6" refy="4" orient="auto"> <path d="M 1 1 7 4 1 7 Z" class="marker"/> </marker> <linearGradient id="btn-light" x1="0%" y1="0%" x2="0%" y2="100%" spreadMethod="pad"> <stop stop-color="hsl(0, 0%, 90%)" offset="0"></stop> <stop stop-color="hsl(0, 0%, 92%)" offset="0.05"></stop> <stop stop-color="hsl(0, 0%, 97%)" offset="0.33"></stop> <stop stop-color="hsl(0, 0%, 87%)" offset="0.67"></stop> <stop stop-color="hsl(0, 0%, 84%)" offset="1"></stop> </linearGradient> </defs> <g class="viz"> <path id="start-path" class="layout-path" d="M210,250 a40,40 0 1,0 80,0 a40,40 0 1,0 -80,0"></path> <path id="quad-path" class="layout-path" d="M165,250 a85,85 0 1,0 170,0 a85,85 0 1,0 -170,0"></path> <path id="mid-path" class="layout-path" d="M110,250 a140,140 0 1,0 280,0 a140,140 0 1,0 -280,0"></path> <path id="end-path" class="layout-path" d="M10,250 a240,240 0 1,0 480,0 a240,240 0 1,0 -480,0"></path> </g> <g id="cycle-btn" transform="translate(186,518)"> <rect class="btn-light" x="0" y="0" rx="4" ry="4" width="128" height="32"></rect> <text class="btn-light-txt" x="64" y="22">Cycle Colors</text> </g> </svg> </div><!-- .svg-container--> <div class="splainin"> <h2>Styling SVG Markers</h2> <p>Using an array of color keys, this demo:</p> <ul> <li>appends CSS for each color to the stylesheet</li> <li>clones svg markers for each color into the defs</li> <li>creates a path for each color, with markers</li> <li>cycles the CSS color class to the next path</li> <li></li> <li></li> </ul> <p><strong><em>Thanks to:</em></strong></p> <p>O'Reilly <a href="https://shop.oreilly.com/product/9780596002237.do" target="_blank">SVG essentials</a> for the quadratic beziƩr curve.</p> <p>Jacob Jenkov for <a href="https://tutorials.jenkov.com/svg/marker-element.html" target="_blank">SVG markers</a>.</p> <p>David Walsh for <a href="https://davidwalsh.name/add-rules-stylesheets" target="_blank">adding styles</a>.</p> <p>complexdan for <a href="https://complexdan.com/svg-circleellipse-to-path-converter/" target="_blank">SVG circles to paths</a>.</p> <p><a href="https://plus.google.com/+PaulIrish/posts" target="_blank"><span class="pi">Paul Irish</span></a> for <a href="https://mothereffinghsl.com/" target="_blank"><span class="mef">m</span><span class="mef">o</span><span class="mef">t</span><span class="mef">h</span><span class="mef">e</span><span class="mef">r</span><span class="mef">e</span><span class="mef">f</span><span class="mef">f</span><span class="mef">i</span><span class="mef">n</span><span class="mef">'</span> <span class="mef">h</span><span class="mef">s</span><span class="mef">l</span></a>.</p> </div> </div> <script> window.addEventListener('DOMContentLoaded', function () { "use strict"; // rainbow starting with blue var colors = ['hsl-242', 'hsl-259', 'hsl-273', 'hsl-296', 'hsl-341', 'hsl-359', 'hsl-18', 'hsl-35', 'hsl-52', 'hsl-83', 'hsl-127', 'hsl-160', 'hsl-190', 'hsl-212', 'hsl-227']; // add styles to the page for colors and SVG paths var addStyles = function () { // select the <style> tag var styles = document.querySelector('#styles'); // select the <style> tag's CSSStyleSheet var styleSheet = styles.sheet; // create styles for the colors, the paths, and the markers colors.forEach(function (color) { var hslColor = color.replace( '-', '(' ) + ', 100%, 50%)'; var colorStyle = '.' + color + ' { stroke: ' + hslColor + '; fill: ' + hslColor + '; color: ' + hslColor+ '; }'; var pathStyle = '.color-path.' + color + ' { ' + 'marker-start: url(#marker-circle-' + color + '); ' + 'marker-mid: url(#marker-square-' + color + '); ' + 'marker-end: url(#marker-arrow-' + color + '); ' + '; }'; // index 0 to add to the front of the CSSRuleList, // to avoid adding !important to the styles already present styleSheet.insertRule(pathStyle, 0); styleSheet.insertRule(colorStyle, 0); }); }; // create def elements for each color and marker type; append to defs var addDefs = function () { var defs = document.querySelector('defs'); // add the appropriate id to the defs element, add the appropriate class to its marker var colorDef = function (def, color) { // add a color class to the def element's marker child var marker = def.querySelector('.marker'); marker.classList.add(color); // set the appropriate id on the def element def.id = def.id + '-' + color; }; colors.forEach(function (color) { // for each color, select and clone the def element for each marker var defArray = []; defArray.push(defs.querySelector('#marker-circle').cloneNode(true)); defArray.push(defs.querySelector('#marker-square').cloneNode(true)); defArray.push(defs.querySelector('#marker-arrow').cloneNode(true)); defArray.forEach(function (def) { colorDef(def, color); defs.appendChild(def); }); }); }; // create def elements for each color and marker type; append to defs var addMefs = function () { var forEach = Array.prototype.forEach; var mefs = document.querySelectorAll('span.mef'); colors.forEach(function ( color, cx ) { if ( mefs[cx] ) { mefs[cx].classList.add(color); } }); }; // create arrays of x,y objects for the start, middle and end of paths var crtCtrlPts = function (markerPos) { var ctrlArr = []; var ctrlPath = document.querySelector('#' + markerPos + '-path') var getPts = function (path) { var pathLen = path.getTotalLength(); var pathPart = 1 / colors.length; colors.forEach(function (color, cx) { var pathTo = pathLen * ( pathPart * (cx + 1) ); if ( markerPos === 'quad' ) { pathTo -= 25; } var gp = ctrlPath.getPointAtLength( pathTo ); var pt = { x: Math.round(gp.x), y: Math.round(gp.y) }; ctrlArr.push(pt); }); }; getPts(ctrlPath); return ctrlArr.reverse(); }; // control points for paths var ctrlPts = { start : crtCtrlPts('start'), quad : crtCtrlPts('quad'), mid : crtCtrlPts('mid'), end : crtCtrlPts('end') }; var crtPaths = function () { var viz = document.querySelector('.viz'); var addPath = function(color, cx) { var path = document.createElementNS('https://www.w3.org/2000/svg', 'path'); var pts = {}; var dString; var posArr = Object.keys(ctrlPts); posArr.forEach(function (pos) { var pt = ctrlPts[pos][cx]; pts[pos] = pt; }); path.id = 'qp-' + cx; dString = 'M ' + pts.start.x + ' ' + pts.start.y + ' Q ' + pts.quad.x + ' ' + pts.quad.y + ', ' + pts.mid.x + ' ' + pts.mid.y + ' T ' + pts.end.x + ' ' + pts.end.y; path.setAttribute( 'd', dString ); path.classList.add( 'color-path', color ); viz.appendChild(path); }; colors.forEach(function ( color, cx ) { addPath( color, cx ); }); }; var forEach = Array.prototype.forEach; var colorsLength = colors.length; var cycleTo; var cycleMs = 50; var cycleColors = function ( paths, mefs ) { var nextColor = function(colorClass) { var colorX = colors.indexOf(colorClass); colorX -= 1; if ( colorX < 0 ) { colorX = colors.length -1; } return colors[colorX]; }; forEach.call( paths, function ( path ) { var colorClass = path.getAttribute('class') .replace('color-path ', ''); var nextHsl = nextColor(colorClass); path.setAttribute('class', 'color-path ' + nextHsl); }); forEach.call( mefs, function ( mef ) { var colorClass = mef.className.replace('mef ', ''); var nextHsl = nextColor(colorClass); mef.className = 'mef ' + nextHsl; }); cycleTo = setTimeout(function () { cycleColors( paths, mefs ); }, cycleMs ); }; document.querySelector('#cycle-btn').addEventListener('click', function(evt) { var cycleTxt = document.querySelector('.btn-light-txt'); var cycleStr = cycleTxt.innerHTML; var paths, mefs; evt.stopPropagation(); if ( cycleStr === 'Stop' ) { clearTimeout(cycleTo); cycleTxt.innerHTML = 'Cycle Colors'; } else { paths = document.querySelectorAll('.viz .color-path'); mefs = document.querySelectorAll('span.mef'); cycleTxt.innerHTML = 'Stop'; cycleTo = setTimeout(function () { cycleColors( paths, mefs ); }, cycleMs ); } }); addStyles(); addDefs(); addMefs() crtPaths(); }); </script> </body> </html>