textures icon indicating copy to clipboard operation
textures copied to clipboard

textures.scale?

Open bollwyvl opened this issue 9 years ago • 12 comments

Here's the shortest way I saw at present to represent a scale of textures: http://codepen.io/anon/pen/wBRwvj

Perhaps a textures.scale, necessarily ordinal, could make this easier:

scale = textures.scale.lines()
  .domain(data)
  .strokeWidth(function(d){ return d.value; });

svg.call(scale.init()) // compute all the textures needed, insert the defs

svg.selectAll("circle")
  .data(data)
  .enter()
  .style({fill: scale})

bollwyvl avatar Mar 18 '15 18:03 bollwyvl

You can still use the normal scales to map between the domain and a range that would be appropriate for the strokeWidth. In other words, I don't think it's essential to mix in domains, since you can do the domain<->configuration mapping elsewhere using the existing scales, no?

I.e.

strokeWidthScale = d3.scale.linear()
   .domain([dataMin, dataMax])
   .range([1, maxStrokeWidth])

textures.lines()
   .strokeWidth(function(d) { return strokeWidthScale(d.value); };

I will however chime in that using the strokeWidth in this way (by sending in a function) — as is possible in most places in D3 — would be very handy indeed, and I actually tried doing something similar to this just thinking it would work.

oskarols avatar Mar 18 '15 20:03 oskarols

Yeah, d3.functor for the win.

However, due to how the svg pattern thing works, one must actually create DOM for each background in defs, so a pure scale won't work... There needs to be a selectall someplace. This could be hidden, such that it was just in time, i.e. a call to url() would figure out if the desired texture actually exists, but explicit may be better... This would allow for reconfiguring.

I'll work up a better example that mixes color and strokewidth, maybe the example grid in the doc.

bollwyvl avatar Mar 19 '15 14:03 bollwyvl

Here's a thing that creates a stepped scale in color and stroke:

http://codepen.io/anon/pen/gbZMLy

It uses several scales to generate the resulting scale, but I can see the benefit to having a factory that encapsulated this pattern in a few lines.

I don't know how one would go about encapsulating multiple path types and then handling their various options.

bollwyvl avatar Mar 19 '15 15:03 bollwyvl

@bollwyvl the pattern you are proposing is, I think, the right way to do that kind of thing, and the benefit to having a factory that simplifies the code is clear. However, I have some doubts it has benefits from the point of view of the designer. I explain myself: textures should not be used to represent quantitative variables, and the introduction of a "scale" method induce to use textures in that way. I imagine the designer choosing carefully a pattern for every qualitative variable (usually few categories), and such kind of process involve pattern definitions that in most of the cases does not follow a scale. Actually I expect a sort of "trial and error" process that ends once the graphical representation satisfies the designer taste.

For that reason I suggest to wait and see what people needs. Let see if textures are used first of all.

riccardoscalco avatar Mar 23 '15 15:03 riccardoscalco

I think your star count indicates some people are interested! It will be interesting to see who uses it!

Looking at this: https://github.com/riccardoscalco/textures/blob/gh-pages/map.js#L46 I see a scale.

It could be that it is a completely non-data-driven, and purely aesthetic, but even then it seems it could be stored in a more compact manner than a switch statement!

But understood if you wish to close this!

bollwyvl avatar Mar 23 '15 17:03 bollwyvl

Well, you are right with the switch! As it is, that piece of code is simply a mistake, indeed it can be replaced by a random choice.

(It is there because my first attempt was to visualize some real data within the intro map and, not having a datasheet (csv/json) ready, I decided to hard code data into the script. At the end of the process I changed idea and I went for a random map, without removing the code.)

It could be that it is a completely non-data-driven, and purely aesthetic, but even then it seems it could be stored in a more compact manner than a switch statement!

There is the possibility I am missing the point of you are suggesting, so I did an example: http://codepen.io/riccardoscalco/pen/MYLaVb?editors=101 In that example the designer chose three well different patterns to identify three categories. Saying that pattern definitions do not follow a scale I mean that they are not functions. In which way the definition of a scale method can help where the patterns are not data-driven?

In the case of 1000 circles to fill with 3 patterns it is possible to use the code you suggested

scale = d3.scale.ordinal()
    .domain [category1, category2, category3]
    .range [t1.url(), t2.url(), t3.url()]

svg.selectAll "circle"
     .data data
  .enter()
    .append "circle"
    .style "fill", (d) -> scale(d.category)

The US map on the website has only line patterns, the idea is to represent an ordered relation and therefore there is a scale somehow, but even in this case I think the designer will design by hand the few patterns she will need (they must be few otherwise the result is a mess).

riccardoscalco avatar Mar 23 '15 22:03 riccardoscalco

Btw, I want to add this example to the thread: http://bl.ocks.org/curran/0ad2eef56811e04f3aa6

riccardoscalco avatar Mar 24 '15 08:03 riccardoscalco

Ideally a texture scale could be defined that uses three independent scales for

  1. Pattern (the base texture)
  2. Size
  3. Color

This would match Bertin's conception of retinal variables: bertin1982

Here is one way of making texture scales that combine pattern, size, and color. http://bl.ocks.org/curran/04cd8c28e06facc55bd7

This includes the just-in-time approach suggested by @bollwyvl.

Because textures.js has an API that mutates the created texture (rather than creating a new texture) when you call modifiers like size(), fill(), and stroke(), the pattern scale needs to actually contain generators for textures, so the other scales (Size, Color) can modify them.

If the textures.js API were purely functional, I think this issue of combined scales would have a cleaner solution. Then, the pattern scale could use straight textures, rather than having to use texture generators. As an example of what I mean, if the API were purely functional, then the following code would work:

var svg = d3.select("#example").append("svg");

var t1 = textures.lines().thicker();
var t2 = t1.fill("red") // This could create a new texture instance

// t1 and t2 would have separate defs in the SVG
svg.call(t1);
svg.call(t2);

svg.append("circle").style("fill", t1.url());
svg.append("circle").style("fill", t2.url());

curran avatar Mar 24 '15 18:03 curran

Love the bertin image! Well have to study that in more depth.

The svg api drives the textures api, hard to avoid that entirely.

I think the "always be copying" api would be confusing, though an explicit texture.copy(), like d3.scale.linear.copy(), would be great.

It would be possible to smooth over the api some, at a cost of performance and deception.

an element in an svg can always know its svg. Since these textures must be singletons anyway a la #id, and since it can only ever drive "fill"... one could remove the svg.call(t1) and t1.url(). Then it would just be shape.call(t1), which would be much more d3-like. Under the hood, it would selectAll("defs #some-texture-id", [t]) and then the whole enter/update dance... But finally selection.style("fill", t.url()).

Unless i am mistaken.

This would have the unintended consequence of updating all uses of the texture, though.

Then yes, a texture.scale could be a generator... But with (necessary) side effects... Maybe worth investigating more! On Mar 24, 2015 2:59 PM, "Curran Kelleher" [email protected] wrote:

Ideally a texture scale could be defined that uses three independent scales for

  1. Pattern (the base texture)
  2. Size
  3. Color

This would match Bertin's conception of retinal variables: [image: bertin1982] http://th-mayer.de/bochum2013/#/9

Here is one way of making texture scales that combine pattern, size, and color. http://bl.ocks.org/curran/04cd8c28e06facc55bd7

Because textures.js has an API that mutates the created texture (rather than creating a new texture) when you call modifiers like size(), fill(), and stroke(), the pattern scale needs to actually contain generators for textures, so the other scales (Size, Color) can modify them.

If the textures.js API were purely functional, I think this issue of combined scales would have a cleaner solution. Then, the pattern scale could use straight textures, rather than having to use texture generators. As an example of what I mean, if the API were purely functional, then the following code would work:

var svg = d3.select("#example").append("svg");

var t1 = textures.lines().thicker(); var t2 = t1.fill("red") // This could create a new texture instance

// t1 and t2 would have separate defs in the SVG svg.call(t1); svg.call(t2);

svg.append("circle").style("fill", t1.url()); svg.append("circle").style("fill", t2.url());

— Reply to this email directly or view it on GitHub https://github.com/riccardoscalco/textures/issues/7#issuecomment-85643515 .

bollwyvl avatar Mar 24 '15 22:03 bollwyvl

Oh yes definitely, adding a copy() or clone() method would do the trick. Great idea!

curran avatar Mar 25 '15 05:03 curran

A lot of good ideas here, I added two new issues and suggest to leave open this one. Thanks for the code @curran !

riccardoscalco avatar Mar 25 '15 10:03 riccardoscalco

Great! My pleasure, thanks for the nice library.

curran avatar Mar 25 '15 16:03 curran