@@ -354,6 +354,185 @@ drawing.gradient = function(sel, gd, gradientID, type, colorscale, prop) {
354354 fullLayout . _gradientUrlQueryParts [ k ] = 1 ;
355355} ;
356356
357+ /**
358+ * pattern: create and apply a pattern fill
359+ *
360+ * @param {object } sel: d3 selection to apply this pattern to
361+ * You can use `selection.call(Drawing.pattern, ...)`
362+ * @param {DOM element } gd: the graph div `sel` is part of
363+ * @param {string } patternID: a unique (within this plot) identifier
364+ * for this pattern, so that we don't create unnecessary definitions
365+ * @param {string } bgcolor: background color for this pattern
366+ * @param {string } fgcolor: foreground color for this pattern
367+ * @param {number } size: size of unit squares for repetition of this pattern
368+ * @param {number } solidity: how solid lines of this pattern are
369+ * @param {string } prop: the property to apply to, 'fill' or 'stroke'
370+ */
371+ drawing . pattern = function ( sel , gd , patternID , shape , bgcolor , fgcolor , size , solidity , prop ) {
372+ var fullLayout = gd . _fullLayout ;
373+ var fullID = 'p' + fullLayout . _uid + '-' + patternID ;
374+ var width , height ;
375+
376+ // linear interpolation
377+ var linearFn = function ( x , x0 , x1 , y0 , y1 ) {
378+ return y0 + ( y1 - y0 ) * ( x - x0 ) / ( x1 - x0 ) ;
379+ } ;
380+
381+ var path , linewidth , radius ;
382+ var patternTag ;
383+ var patternAttrs = { } ;
384+ switch ( shape ) {
385+ case '/' :
386+ width = size * Math . sqrt ( 2 ) ;
387+ height = size * Math . sqrt ( 2 ) ;
388+ path = 'M-' + ( width / 4 ) + ',' + ( height / 4 ) + 'l' + ( width / 2 ) + ',-' + ( height / 2 ) +
389+ 'M0,' + height + 'L' + width + ',0' +
390+ 'M' + ( width / 4 * 3 ) + ',' + ( height / 4 * 5 ) + 'l' + ( width / 2 ) + ',-' + ( height / 2 ) ;
391+ linewidth = solidity * size ;
392+ patternTag = 'path' ;
393+ patternAttrs = {
394+ 'd' : path ,
395+ 'stroke' : fgcolor ,
396+ 'stroke-width' : linewidth + 'px'
397+ } ;
398+ break ;
399+ case '\\' :
400+ width = size * Math . sqrt ( 2 ) ;
401+ height = size * Math . sqrt ( 2 ) ;
402+ path = 'M' + ( width / 4 * 3 ) + ',-' + ( height / 4 ) + 'l' + ( width / 2 ) + ',' + ( height / 2 ) +
403+ 'M0,0L' + width + ',' + height +
404+ 'M-' + ( width / 4 ) + ',' + ( height / 4 * 3 ) + 'l' + ( width / 2 ) + ',' + ( height / 2 ) ;
405+ linewidth = solidity * size ;
406+ patternTag = 'path' ;
407+ patternAttrs = {
408+ 'd' : path ,
409+ 'stroke' : fgcolor ,
410+ 'stroke-width' : linewidth + 'px'
411+ } ;
412+ break ;
413+ case 'x' :
414+ width = size * Math . sqrt ( 2 ) ;
415+ height = size * Math . sqrt ( 2 ) ;
416+ path = 'M-' + ( width / 4 ) + ',' + ( height / 4 ) + 'l' + ( width / 2 ) + ',-' + ( height / 2 ) +
417+ 'M0,' + height + 'L' + width + ',0' +
418+ 'M' + ( width / 4 * 3 ) + ',' + ( height / 4 * 5 ) + 'l' + ( width / 2 ) + ',-' + ( height / 2 ) +
419+ 'M' + ( width / 4 * 3 ) + ',-' + ( height / 4 ) + 'l' + ( width / 2 ) + ',' + ( height / 2 ) +
420+ 'M0,0L' + width + ',' + height +
421+ 'M-' + ( width / 4 ) + ',' + ( height / 4 * 3 ) + 'l' + ( width / 2 ) + ',' + ( height / 2 ) ;
422+ linewidth = size - size * Math . sqrt ( 1.0 - solidity ) ;
423+ patternTag = 'path' ;
424+ patternAttrs = {
425+ 'd' : path ,
426+ 'stroke' : fgcolor ,
427+ 'stroke-width' : linewidth + 'px'
428+ } ;
429+ break ;
430+ case '|' :
431+ width = size ;
432+ height = size ;
433+ patternTag = 'path' ;
434+ path = 'M' + ( width / 2 ) + ',0L' + ( width / 2 ) + ',' + height ;
435+ linewidth = solidity * size ;
436+ patternTag = 'path' ;
437+ patternAttrs = {
438+ 'd' : path ,
439+ 'stroke' : fgcolor ,
440+ 'stroke-width' : linewidth + 'px'
441+ } ;
442+ break ;
443+ case '-' :
444+ width = size ;
445+ height = size ;
446+ patternTag = 'path' ;
447+ path = 'M0,' + ( height / 2 ) + 'L' + width + ',' + ( height / 2 ) ;
448+ linewidth = solidity * size ;
449+ patternTag = 'path' ;
450+ patternAttrs = {
451+ 'd' : path ,
452+ 'stroke' : fgcolor ,
453+ 'stroke-width' : linewidth + 'px'
454+ } ;
455+ break ;
456+ case '+' :
457+ width = size ;
458+ height = size ;
459+ patternTag = 'path' ;
460+ path = 'M' + ( width / 2 ) + ',0L' + ( width / 2 ) + ',' + height +
461+ 'M0,' + ( height / 2 ) + 'L' + width + ',' + ( height / 2 ) ;
462+ linewidth = size - size * Math . sqrt ( 1.0 - solidity ) ;
463+ patternTag = 'path' ;
464+ patternAttrs = {
465+ 'd' : path ,
466+ 'stroke' : fgcolor ,
467+ 'stroke-width' : linewidth + 'px'
468+ } ;
469+ break ;
470+ case '.' :
471+ width = size ;
472+ height = size ;
473+ if ( solidity < Math . PI / 4 ) {
474+ radius = Math . sqrt ( solidity * size * size / Math . PI ) ;
475+ } else {
476+ radius = linearFn ( solidity , Math . PI / 4 , 1.0 , size / 2 , size / Math . sqrt ( 2 ) ) ;
477+ }
478+ patternTag = 'circle' ;
479+ patternAttrs = {
480+ 'cx' : width / 2 ,
481+ 'cy' : height / 2 ,
482+ 'r' : radius ,
483+ 'fill' : fgcolor
484+ } ;
485+ break ;
486+ }
487+
488+ var pattern = fullLayout . _defs . select ( '.patterns' )
489+ . selectAll ( '#' + fullID )
490+ . data ( [ shape + ';' + bgcolor + ';' + fgcolor + ';' + size + ';' + solidity ] , Lib . identity ) ;
491+
492+ pattern . exit ( ) . remove ( ) ;
493+
494+ pattern . enter ( )
495+ . append ( 'pattern' )
496+ . each ( function ( ) {
497+ var el = d3 . select ( this ) ;
498+
499+ el . attr ( {
500+ 'id' : fullID ,
501+ 'width' : width + 'px' ,
502+ 'height' : height + 'px' ,
503+ 'patternUnits' : 'userSpaceOnUse'
504+ } ) ;
505+
506+ if ( bgcolor ) {
507+ var rects = el . selectAll ( 'rect' ) . data ( [ 0 ] ) ;
508+ rects . exit ( ) . remove ( ) ;
509+ rects . enter ( )
510+ . append ( 'rect' )
511+ . attr ( {
512+ 'width' : width + 'px' ,
513+ 'height' : height + 'px' ,
514+ 'fill' : bgcolor
515+ } ) ;
516+ }
517+
518+ var patterns = el . selectAll ( patternTag ) . data ( [ 0 ] ) ;
519+ patterns . exit ( ) . remove ( ) ;
520+ patterns . enter ( )
521+ . append ( patternTag )
522+ . attr ( patternAttrs ) ;
523+ } ) ;
524+
525+ sel . style ( prop , getFullUrl ( fullID , gd ) )
526+ . style ( prop + '-opacity' , null ) ;
527+
528+ sel . classed ( 'pattern_filled' , true ) ;
529+ var className2query = function ( s ) {
530+ return '.' + s . attr ( 'class' ) . replace ( / \s / g, '.' ) ;
531+ } ;
532+ var k = className2query ( d3 . select ( sel . node ( ) . parentNode ) ) + '>.pattern_filled' ;
533+ fullLayout . _patternUrlQueryParts [ k ] = 1 ;
534+ } ;
535+
357536/*
358537 * Make the gradients container and clear out any previous gradients.
359538 * We never collect all the gradients we need in one place,
@@ -372,6 +551,23 @@ drawing.initGradients = function(gd) {
372551 fullLayout . _gradientUrlQueryParts = { } ;
373552} ;
374553
554+ drawing . initPatterns = function ( gd ) {
555+ var fullLayout = gd . _fullLayout ;
556+
557+ var patternsGroup = Lib . ensureSingle ( fullLayout . _defs , 'g' , 'patterns' ) ;
558+ patternsGroup . selectAll ( 'pattern' ) . remove ( ) ;
559+
560+ // initialize stash of query parts filled in Drawing.pattern,
561+ // used to fix URL strings during image exports
562+ fullLayout . _patternUrlQueryParts = { } ;
563+ } ;
564+
565+ drawing . getPatternAttr = function ( mp , i , dflt ) {
566+ if ( mp && Lib . isArrayOrTypedArray ( mp ) ) {
567+ return i < mp . length ? mp [ i ] : dflt ;
568+ }
569+ return mp ;
570+ } ;
375571
376572drawing . pointStyle = function ( s , trace , gd ) {
377573 if ( ! s . size ( ) ) return ;
@@ -477,11 +673,14 @@ drawing.singlePointStyle = function(d, sel, trace, fns, gd) {
477673
478674 // for legend - arrays will propagate through here, but we don't need
479675 // to treat it as per-point.
480- if ( Array . isArray ( gradientType ) ) {
676+ if ( Lib . isArrayOrTypedArray ( gradientType ) ) {
481677 gradientType = gradientType [ 0 ] ;
482678 if ( ! gradientInfo [ gradientType ] ) gradientType = 0 ;
483679 }
484680
681+ var markerPattern = marker . pattern ;
682+ var patternShape = markerPattern && drawing . getPatternAttr ( markerPattern . shape , d . i , '' ) ;
683+
485684 if ( gradientType && gradientType !== 'none' ) {
486685 var gradientColor = d . mgc ;
487686 if ( gradientColor ) perPointGradient = true ;
@@ -492,6 +691,20 @@ drawing.singlePointStyle = function(d, sel, trace, fns, gd) {
492691
493692 drawing . gradient ( sel , gd , gradientID , gradientType ,
494693 [ [ 0 , gradientColor ] , [ 1 , fillColor ] ] , 'fill' ) ;
694+ } else if ( patternShape ) {
695+ var patternBGColor = drawing . getPatternAttr ( markerPattern . bgcolor , d . i , null ) ;
696+ var patternSize = drawing . getPatternAttr ( markerPattern . size , d . i , 8 ) ;
697+ var patternSolidity = drawing . getPatternAttr ( markerPattern . solidity , d . i , 0.3 ) ;
698+ var perPointPattern = Lib . isArrayOrTypedArray ( markerPattern . shape ) ||
699+ Lib . isArrayOrTypedArray ( markerPattern . bgcolor ) ||
700+ Lib . isArrayOrTypedArray ( markerPattern . size ) ||
701+ Lib . isArrayOrTypedArray ( markerPattern . solidity ) ;
702+
703+ var patternID = trace . uid ;
704+ if ( perPointPattern ) patternID += '-' + d . i ;
705+
706+ drawing . pattern ( sel , gd , patternID , patternShape , patternBGColor , fillColor ,
707+ patternSize , patternSolidity , 'fill' ) ;
495708 } else {
496709 Color . fill ( sel , fillColor ) ;
497710 }
0 commit comments