-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add pattern fill for histogram, bar and barpolar traces #5520
Changes from 5 commits
8f53e11
863854f
39e35cc
9f7ce1d
6e9cb40
5161478
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -354,6 +354,184 @@ drawing.gradient = function(sel, gd, gradientID, type, colorscale, prop) { | |
fullLayout._gradientUrlQueryParts[k] = 1; | ||
}; | ||
|
||
/** | ||
* pattern: create and apply a pattern fill | ||
* | ||
* @param {object} sel: d3 selection to apply this pattern to | ||
* You can use `selection.call(Drawing.pattern, ...)` | ||
* @param {DOM element} gd: the graph div `sel` is part of | ||
* @param {string} patternID: a unique (within this plot) identifier | ||
* for this pattern, so that we don't create unnecessary definitions | ||
* @param {string} bgcolor: background color for this pattern | ||
* @param {string} fgcolor: foreground color for this pattern | ||
* @param {number} size: size of unit squares for repetition of this pattern | ||
* @param {number} solidity: how solid lines of this pattern are | ||
* @param {string} prop: the property to apply to, 'fill' or 'stroke' | ||
*/ | ||
drawing.pattern = function(sel, gd, patternID, shape, bgcolor, fgcolor, size, solidity, prop) { | ||
var fullLayout = gd._fullLayout; | ||
var fullID = 'p' + fullLayout._uid + '-' + patternID; | ||
var width, height; | ||
|
||
var path, linewidth, radius; | ||
var patternTag; | ||
var patternAttrs = {}; | ||
switch(shape) { | ||
case '/': | ||
width = size * Math.sqrt(2); | ||
height = size * Math.sqrt(2); | ||
path = 'M-' + (width / 4) + ',' + (height / 4) + 'l' + (width / 2) + ',-' + (height / 2) + | ||
'M0,' + height + 'L' + width + ',0' + | ||
'M' + (width / 4 * 3) + ',' + (height / 4 * 5) + 'l' + (width / 2) + ',-' + (height / 2); | ||
linewidth = solidity * size; | ||
patternTag = 'path'; | ||
patternAttrs = { | ||
'd': path, | ||
'stroke': fgcolor, | ||
'stroke-width': linewidth + 'px' | ||
}; | ||
break; | ||
case '\\': | ||
width = size * Math.sqrt(2); | ||
height = size * Math.sqrt(2); | ||
path = 'M' + (width / 4 * 3) + ',-' + (height / 4) + 'l' + (width / 2) + ',' + (height / 2) + | ||
'M0,0L' + width + ',' + height + | ||
'M-' + (width / 4) + ',' + (height / 4 * 3) + 'l' + (width / 2) + ',' + (height / 2); | ||
linewidth = solidity * size; | ||
patternTag = 'path'; | ||
patternAttrs = { | ||
'd': path, | ||
'stroke': fgcolor, | ||
'stroke-width': linewidth + 'px' | ||
}; | ||
break; | ||
case 'x': | ||
width = size * Math.sqrt(2); | ||
height = size * Math.sqrt(2); | ||
path = 'M-' + (width / 4) + ',' + (height / 4) + 'l' + (width / 2) + ',-' + (height / 2) + | ||
'M0,' + height + 'L' + width + ',0' + | ||
'M' + (width / 4 * 3) + ',' + (height / 4 * 5) + 'l' + (width / 2) + ',-' + (height / 2) + | ||
'M' + (width / 4 * 3) + ',-' + (height / 4) + 'l' + (width / 2) + ',' + (height / 2) + | ||
'M0,0L' + width + ',' + height + | ||
'M-' + (width / 4) + ',' + (height / 4 * 3) + 'l' + (width / 2) + ',' + (height / 2); | ||
linewidth = size - size * Math.sqrt(1.0 - solidity); | ||
patternTag = 'path'; | ||
patternAttrs = { | ||
'd': path, | ||
'stroke': fgcolor, | ||
'stroke-width': linewidth + 'px' | ||
}; | ||
break; | ||
case '|': | ||
width = size; | ||
height = size; | ||
patternTag = 'path'; | ||
path = 'M' + (width / 2) + ',0L' + (width / 2) + ',' + height; | ||
linewidth = solidity * size; | ||
patternTag = 'path'; | ||
patternAttrs = { | ||
'd': path, | ||
'stroke': fgcolor, | ||
'stroke-width': linewidth + 'px' | ||
}; | ||
break; | ||
case '-': | ||
width = size; | ||
height = size; | ||
patternTag = 'path'; | ||
path = 'M0,' + (height / 2) + 'L' + width + ',' + (height / 2); | ||
linewidth = solidity * size; | ||
patternTag = 'path'; | ||
patternAttrs = { | ||
'd': path, | ||
'stroke': fgcolor, | ||
'stroke-width': linewidth + 'px' | ||
}; | ||
break; | ||
case '+': | ||
width = size; | ||
height = size; | ||
patternTag = 'path'; | ||
path = 'M' + (width / 2) + ',0L' + (width / 2) + ',' + height + | ||
'M0,' + (height / 2) + 'L' + width + ',' + (height / 2); | ||
linewidth = size - size * Math.sqrt(1.0 - solidity); | ||
patternTag = 'path'; | ||
patternAttrs = { | ||
'd': path, | ||
'stroke': fgcolor, | ||
'stroke-width': linewidth + 'px' | ||
}; | ||
break; | ||
case '.': | ||
width = size; | ||
height = size; | ||
if(solidity < Math.PI / 4) { | ||
radius = Math.sqrt(solidity * size * size / Math.PI); | ||
} else { | ||
// linear interpolation | ||
var linearFn = function(x, x0, x1, y0, y1) { | ||
return y0 + (y1 - y0) * (x - x0) / (x1 - x0); | ||
}; | ||
radius = linearFn(solidity, Math.PI / 4, 1.0, size / 2, size / Math.sqrt(2)); | ||
} | ||
patternTag = 'circle'; | ||
patternAttrs = { | ||
'cx': width / 2, | ||
'cy': height / 2, | ||
'r': radius, | ||
'fill': fgcolor | ||
}; | ||
break; | ||
} | ||
|
||
var pattern = fullLayout._defs.select('.patterns') | ||
.selectAll('#' + fullID) | ||
.data([shape + ';' + bgcolor + ';' + fgcolor + ';' + size + ';' + solidity], Lib.identity); | ||
|
||
pattern.exit().remove(); | ||
|
||
pattern.enter() | ||
.append('pattern') | ||
.each(function() { | ||
var el = d3.select(this); | ||
|
||
el.attr({ | ||
'id': fullID, | ||
'width': width + 'px', | ||
'height': height + 'px', | ||
'patternUnits': 'userSpaceOnUse' | ||
}); | ||
|
||
if(bgcolor) { | ||
var rects = el.selectAll('rect').data([0]); | ||
rects.exit().remove(); | ||
rects.enter() | ||
.append('rect') | ||
.attr({ | ||
'width': width + 'px', | ||
'height': height + 'px', | ||
'fill': bgcolor | ||
}); | ||
} | ||
|
||
var patterns = el.selectAll(patternTag).data([0]); | ||
patterns.exit().remove(); | ||
patterns.enter() | ||
.append(patternTag) | ||
.attr(patternAttrs); | ||
}); | ||
|
||
sel.style(prop, getFullUrl(fullID, gd)) | ||
.style(prop + '-opacity', null); | ||
|
||
sel.classed('pattern_filled', true); | ||
var className2query = function(s) { | ||
return '.' + s.attr('class').replace(/\s/g, '.'); | ||
}; | ||
var k = className2query(d3.select(sel.node().parentNode)) + '>.pattern_filled'; | ||
fullLayout._patternUrlQueryParts[k] = 1; | ||
}; | ||
|
||
/* | ||
* Make the gradients container and clear out any previous gradients. | ||
* We never collect all the gradients we need in one place, | ||
|
@@ -372,6 +550,16 @@ drawing.initGradients = function(gd) { | |
fullLayout._gradientUrlQueryParts = {}; | ||
}; | ||
|
||
drawing.initPatterns = function(gd) { | ||
var fullLayout = gd._fullLayout; | ||
|
||
var patternsGroup = Lib.ensureSingle(fullLayout._defs, 'g', 'patterns'); | ||
patternsGroup.selectAll('pattern').remove(); | ||
|
||
// initialize stash of query parts filled in Drawing.pattern, | ||
// used to fix URL strings during image exports | ||
fullLayout._patternUrlQueryParts = {}; | ||
}; | ||
|
||
drawing.pointStyle = function(s, trace, gd) { | ||
if(!s.size()) return; | ||
|
@@ -482,6 +670,16 @@ drawing.singlePointStyle = function(d, sel, trace, fns, gd) { | |
if(!gradientInfo[gradientType]) gradientType = 0; | ||
} | ||
|
||
var getPatternAttr = function(mp, i, dflt) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Non-blocking: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in 5161478 |
||
if(mp && Array.isArray(mp)) { | ||
if(i < mp.length) return mp[i]; | ||
else return dflt; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about simply return i < mp.length ? mp[i] : dflt; There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in 5161478 |
||
} | ||
return mp; | ||
}; | ||
var markerPattern = marker.pattern; | ||
var patternShape = markerPattern && getPatternAttr(markerPattern.shape, d.i, ''); | ||
|
||
if(gradientType && gradientType !== 'none') { | ||
var gradientColor = d.mgc; | ||
if(gradientColor) perPointGradient = true; | ||
|
@@ -492,6 +690,20 @@ drawing.singlePointStyle = function(d, sel, trace, fns, gd) { | |
|
||
drawing.gradient(sel, gd, gradientID, gradientType, | ||
[[0, gradientColor], [1, fillColor]], 'fill'); | ||
} else if(patternShape) { | ||
var patternBGColor = getPatternAttr(markerPattern.bgcolor, d.i, null); | ||
var patternSize = getPatternAttr(markerPattern.size, d.i, 8); | ||
var patternSolidity = getPatternAttr(markerPattern.solidity, d.i, 0.3); | ||
var perPointPattern = Array.isArray(markerPattern.shape) || | ||
Array.isArray(markerPattern.bgcolor) || | ||
Array.isArray(markerPattern.size) || | ||
Array.isArray(markerPattern.solidity); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't we use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in 5161478 |
||
|
||
var patternID = trace.uid; | ||
if(perPointPattern) patternID += '-' + d.i; | ||
|
||
drawing.pattern(sel, gd, patternID, patternShape, patternBGColor, fillColor, | ||
patternSize, patternSolidity, 'fill'); | ||
} else { | ||
Color.fill(sel, fillColor); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -357,8 +357,30 @@ module.exports = function style(s, gd, legend) { | |
var d0 = d[0]; | ||
var w = boundLineWidth(d0.mlw, marker.line, MAX_MARKER_LINE_WIDTH, CST_MARKER_LINE_WIDTH); | ||
|
||
p.style('stroke-width', w + 'px') | ||
.call(Color.fill, d0.mc || marker.color); | ||
p.style('stroke-width', w + 'px'); | ||
|
||
var fillColor = d0.mc || marker.color; | ||
|
||
var getPatternAttr = function(mp, dflt) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function looks quite similar to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It could go in drawing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure! Fixed in 5161478 |
||
if(mp && Array.isArray(mp)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we handle There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in 5161478 |
||
if(mp.length > 0) return mp[0]; | ||
else return dflt; | ||
} | ||
return mp; | ||
}; | ||
var markerPattern = marker.pattern; | ||
var patternShape = markerPattern && getPatternAttr(markerPattern.shape, ''); | ||
|
||
if(patternShape) { | ||
var patternBGColor = getPatternAttr(markerPattern.bgcolor, null); | ||
var patternSize = Math.min(12, getPatternAttr(markerPattern.size, 8)); | ||
var patternSolidity = getPatternAttr(markerPattern.solidity, 0.3); | ||
var patternID = 'legend-' + trace.uid; | ||
p.call(Drawing.pattern, gd, patternID, patternShape, patternBGColor, | ||
fillColor, patternSize, patternSolidity, 'fill'); | ||
} else { | ||
p.call(Color.fill, fillColor); | ||
} | ||
|
||
if(w) Color.stroke(p, d0.mlc || markerLine.color); | ||
}); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,7 +33,7 @@ module.exports = function toSVG(gd, format, scale) { | |
var toppaper = fullLayout._toppaper; | ||
var width = fullLayout.width; | ||
var height = fullLayout.height; | ||
var i; | ||
var i, k; | ||
|
||
// make background color a rect in the svg, then revert after scraping | ||
// all other alterations have been dealt with by properly preparing the svg | ||
|
@@ -106,28 +106,31 @@ module.exports = function toSVG(gd, format, scale) { | |
} | ||
}); | ||
|
||
|
||
var queryParts = []; | ||
if(fullLayout._gradientUrlQueryParts) { | ||
var queryParts = []; | ||
for(var k in fullLayout._gradientUrlQueryParts) queryParts.push(k); | ||
|
||
if(queryParts.length) { | ||
svg.selectAll(queryParts.join(',')).each(function() { | ||
var pt = d3.select(this); | ||
|
||
// similar to font family styles above, | ||
// we must remove " after the SVG DOM has been serialized | ||
var fill = this.style.fill; | ||
if(fill && fill.indexOf('url(') !== -1) { | ||
pt.style('fill', fill.replace(DOUBLEQUOTE_REGEX, DUMMY_SUB)); | ||
} | ||
|
||
var stroke = this.style.stroke; | ||
if(stroke && stroke.indexOf('url(') !== -1) { | ||
pt.style('stroke', stroke.replace(DOUBLEQUOTE_REGEX, DUMMY_SUB)); | ||
} | ||
}); | ||
} | ||
for(k in fullLayout._gradientUrlQueryParts) queryParts.push(k); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't we use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this regard, I couldn't see the point in storing query parts as a dict ( Also, I suspect that it makes things more complicated to store query parts globally. How about just putting a dedicated class name for pattern fill (and gradient) and collecting them later? Actually, I already did the similar thing (below). in sel.classed('pattern_filled', true);
var className2query = function(s) {
return '.' + s.attr('class').replace(/\s/g, '.');
};
var k = className2query(d3.select(sel.node().parentNode)) + '>.pattern_filled';
fullLayout._patternUrlQueryParts[k] = 1; I think it is enough to put |
||
} | ||
|
||
if(fullLayout._patternUrlQueryParts) { | ||
for(k in fullLayout._patternUrlQueryParts) queryParts.push(k); | ||
} | ||
|
||
if(queryParts.length) { | ||
svg.selectAll(queryParts.join(',')).each(function() { | ||
var pt = d3.select(this); | ||
|
||
// similar to font family styles above, | ||
// we must remove " after the SVG DOM has been serialized | ||
var fill = this.style.fill; | ||
if(fill && fill.indexOf('url(') !== -1) { | ||
pt.style('fill', fill.replace(DOUBLEQUOTE_REGEX, DUMMY_SUB)); | ||
} | ||
|
||
var stroke = this.style.stroke; | ||
if(stroke && stroke.indexOf('url(') !== -1) { | ||
pt.style('stroke', stroke.replace(DOUBLEQUOTE_REGEX, DUMMY_SUB)); | ||
} | ||
}); | ||
} | ||
|
||
if(format === 'pdf' || format === 'eps') { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you declare this function outside the switch case please?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 5161478