This is a d3 plugin to create a "loom" visualization. For an extensive explanation of the effects of all the settings decribed farther below, please see the blog/tutorial I wrote about the loom here.
The loom layout is meant to create a chart with a group of entities in the center and different group of entities on the outside. They are connected by strings where the thickness of the string on the outside represents the connection (i.e. value) of the inner and outer entity.
Or in the words of Robert Kosara
One chart to rule them all, one chart to find them; one chart to bring them all and in the darkness bind them
For example, in the above image, the inner entities are the characters of the Fellowship in the Lord of the Rings movies. The outer entities are the locations in Middle Earth where the movie takes place. The connection/value is the number of words spoken by each character at each location.
Since this layout was transformed from d3's chord diagram many of the below API references will be similar to those from the chord diagram for the loom and similar to the ribbon functions for the strings.
Download the latest build. d3-loom depends on d3, so be sure to include a script tag with the d3 library before including d3-loom.js
. In a vanilla environment, a d3 global is exported:
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="d3-loom.js"></script>
<script>
var loom = d3.loom();
</script>
If you use a package manager like npm or yarn, say
npm install d3-loom
or
yarn add d3-loom
to add d3-loom to your project. AMD, CommonJS, and vanilla environments are supported.
One thing that I have not (yet) figured out is how to sort the outer groups/entities in such a way to automatically make a visually appealing split in 2 separate halves. This is only relevant when you specify an empty percentage, thus create a gap in the top and bottom. For now you will have to manually order the outer entities in such a way that when split into two groups, the total value of those two groups separately lies close to 50%. However, you don't need to have the exact number of entities on the left half as on the right. The program will try and find a split that separates all the entities in two groups to make them both as close to 50% as possible, but it will not reorder the outer entities to do so.
This is my first attempt at a plugin and it has definitely not been tested well enough. I would therefore love to hear from you about any bugs or errors that you run into while trying to use the plugin. You can create an Issue right here, reach me on Twitter via @NadiehBremer or mail me on info at visualcinnamon.com
# d3.loom()
Constructs a new loom generator with the default settings.
# loom(data)
Computes the loom layout for the specified data. The length of the returned array is the same as data, however, due to sorting of the strings, to reduce overlap, the ordering can be different than the initial data.
Typically a dataset for the loom contains 3 values; the outer entity, the inner entity and the value that connects these two (e.g. outer = location, inner = person, value = days that person stayed in the location):
var data = [
{
outer: "Amsterdam",
inner: "Alexander",
value: 679
},
{
outer: "Bangkok",
inner: "Brendan",
value: 124
},
//...and so on
];
The return value of loom(data) is an array of looms, where each loom represents the connection between an entity in the center of the loom (the inner) and the entity on the outside of the loom (the outer) and is an object with the following properties:
inner
- the inner subgroupouter
- the outer subgroup
Both the inner and outer subgroup are also objects. The inner has the following properties:
name
- the name of the inner entityoffset
- the horizontal offset of the inner string's endpoint from the centerx
- the horizontal location of the inner entityy
- the vertical location of the inner entity
And the outer has the following properties:
groupStartAngle
- the start angle of the outer group to which the specific string belongsstartAngle
- the start angle of the string at the outer edge in radiansendAngle
- the end angle of the string at the outer edge in radiansvalue
- the numeric value of the stringindex
- the zero-based sorted index of the groupsubindex
- the zero-based sorted sub-index of the string within the groupinnername
- the name of the connected inner entityoutername
- the name of the outer entity
The looms are passed to d3.string to display the relationships between the inner and outer entities.
The looms array also defines a two secondary arrays. The first, called groups, is an array representing the outer entities. The length of the array is the number of unique outer entities and is an object with the following properties:
startAngle
- the start angle of the arc in radiansendAngle
- the end angle of the arc in radiansvalue
- the numeric value of the arcindex
- the zero-based sorted index of the arcoutername
- the name of the outer entity
The groups are passed to d3.arc to produce a donut chart around the circumference of the loom layout.
The other array, called, innergroups, is an array represting the inner entities. The length of the array is the number of unique inner entities and is an object with the following properties:
name
- the name of the inner entityoffset
- the horizontal offset of the inner string's endpoint from the centerx
- the horizontal location of the inner entityy
- the vertical location of the inner entity
The innergroups are used to create the textual representation of the inner entities in the center of the loom layout.
# loom.padAngle([angle])
If angle is specified, sets the pad angle (i.e. the white space) between adjacent outer groups to the specified number in radians and returns this loom layout. If angle is not specified, returns the current pad angle, which defaults to zero.
# loom.inner([inner])
The inner represents the name/id/... textual value of the entities that will be placed in the center. If inner is specified, sets the inner accessor to the specified function and returns this string generator. If inner is not specified, returns the current inner accessor, which defaults to:
function inner(d) {
return d.inner;
}
# loom.outer([outer])
The outer represents the name/id/... textual value of the entities that will be placed around the loom along a circle. If outer is specified, sets the outer accessor to the specified function and returns this string generator. If outer is not specified, returns the current outer accessor, which defaults to:
function outer(d) {
return d.outer;
}
# loom.value([value])
The value represents the numeric value that is the connection between the inner and outer entity. It is the value that determines the width of the strings on the outside. If value is specified, sets the value accessor to the specified function and returns this string generator. If value is not specified, returns the current value accessor, which defaults to:
function value(d) {
return d.value;
}
# loom.heightInner([height])
This height gives the vertical distance between the inner entities in the center. If height is specified, sets the heightInner to the specified number and returns this loom generator. If height is not specified, returns the current heightInner value, which defaults to 20 pixels.
# loom.widthInner([width])
This width gives the horizontal distance between the inner endpoints of the strings in the center. It is the value that determines the width of the gap that is created so the text of the inner entities does not overlap the strings. If width is specified, sets the widthInner to the specified function or number and returns this loom generator. However, note that this function receives a d value that contains the string of the entity in the center (the inner). You can therefore make the width depend on the length of the inner's string. If width is not specified, returns the current widthInner value, which defaults to 30 pixels.
# loom.emptyPerc([value])
This value gives the percentage of the circle that will be empty to create space in the top and bottom. If value is specified, sets the current emptyPerc to the specified number in the range [0,1] and returns this loom generator. If value is not specified, returns the current emptyPerc value, which defaults to 0.2.
# loom.sortGroups([compare])
If compare is specified, sets the group comparator to the specified function or null and returns this loom layout. If compare is not specified, returns the current group comparator, which defaults to null. If the group comparator is non-null, it is used to sort the outer groups/entities by their total value (i.e. the sum of all the inner strings). See also d3.ascending and d3.descending.
# loom.sortSubgroups([compare])
If compare is specified, sets the subgroup comparator to the specified function or null and returns this loom layout. If compare is not specified, returns the current subgroup comparator, which defaults to null. If the subgroup comparator is non-null, it is used to sort the subgroups (i.e. the separate strings) within each outer entity by their value. See also d3.ascending and d3.descending. This sorting applies to both the order of the strings on the outer edge and the vertical order of the inner entities. It is advised to supply a subGroup sorting whenever there is not already a sorting applied to the underlying data, otherwise the inner entities and the strings will be drawn in the exact order as they appear in the data, typically resulting in a lot of overlapping strings.
# loom.sortLooms([compare])
If compare is specified, sets the loom comparator to the specified function or null and returns this loom layout. If compare is not specified, returns the current loom comparator, which defaults to null. If the loom comparator is non-null, it is used to sort the strings by their value; this only affects the z-order of these inner strings (i.e. how they overlap). See also d3.ascending and [d3.descending].(https://github.com/d3/d3-array#descending).
# d3.string()
Creates a new string generator with the default settings.
# string(arguments…)
Generates a string for the given arguments. The arguments are arbitrary; they are simply propagated to the string's generator's accessor functions along with the this
object. If the string generator has a context, then the string is rendered to this context as a sequence of path method calls and this function returns void. Otherwise, a path data string is returned.
Typically, only the radius, thicknessInner, and pullout should be adjusted when used on conjunction with the loom, because all the other accessors will work with the default settings.
# string.radius([radius])
If radius is specified, sets the radius accessor to the specified function and returns this string generator. If radius is not specified, returns the current radius value, which defaults to 100 pixels.
The radius represents the inner radius of the loom and is typically set to a single number. It is advised to always set this value different from the default, depending on the space available within your svg.
# string.thicknessInner([thickness])
If thickness is specified, sets the thicknessInner to the specified number and returns this string generator. If thickness is not specified, returns the current thicknessInner value, which defaults to 0 pixels. The thicknessInner defines the "height", or thickness, that the strings will have at their endpoints next to the inner entities.
# string.pullout([pullout])
If pullout is specified, sets the pullout to the specified number and returns this string generator. If pullout is not specified, returns the current pullout value, which defaults to 50 pixels. The pullout defines how far the two circle halves will be placed outward horizontally.
# string.inner([inner])
If inner is specified, sets the inner accessor to the specified function and returns this string generator. If inner is not specified, returns the current source accessor, which defaults to:
function inner(d) {
return d.inner;
}
When the string generator is used in conjunction with the loom, the resulting loom array will contain an inner object. Thus this accessor function does not have to be set separately, but just use the default.
# string.outer([outer])
If outer is specified, sets the outer accessor to the specified function and returns this string generator. If outer is not specified, returns the current outer accessor, which defaults to:
function outer(d) {
return d.outer;
}
When the string generator is used in conjunction with the loom, the resulting loom array will contain an outer object. Thus this accessor function does not have to be set separately, but just use the default.
# string.groupStartAngle([angle])
If angle is specified, sets the start angle accessor to the specified function and returns this string generator. If angle is not specified, returns the current start angle accessor, which defaults to:
function groupStartAngle(d) {
return d.groupStartAngle;
}
The angle is specified in radians, with 0 at -y (12 o'clock) and positive angles proceeding clockwise. This separate assessor is needed to make sure that even when an emptyPerc is set, all the strings belonging to the same outer group will be drawn at the same side. It's best make this assessor similar in "function" to the startAngle below (i.e. if a constant is added onto the startAngle to rotate the whole, then the same constant should be added to the groupStartAngle). When the string generator is used in conjunction with the loom, the resulting loom array will contain an groupStartAngle value within the outer object. In that case this accessor function does not have to be set separately, but just use the default.
# string.startAngle([angle])
If angle is specified, sets the start angle accessor to the specified function and returns this string generator. If angle is not specified, returns the current start angle accessor, which defaults to:
function startAngle(d) {
return d.startAngle;
}
The angle is specified in radians, with 0 at -y (12 o'clock) and positive angles proceeding clockwise. When the string generator is used in conjunction with the loom, the resulting loom array will contain an startAngle value within the outer object. In that case this accessor function does not have to be set separately, but just use the default.
# string.endAngle([angle])
If angle is specified, sets the end angle accessor to the specified function and returns this string generator. If angle is not specified, returns the current end angle accessor, which defaults to:
function endAngle(d) {
return d.endAngle;
}
The angle is specified in radians, with 0 at -y (12 o'clock) and positive angles proceeding clockwise. When the string generator is used in conjunction with the loom, the resulting loom array will contain an endAngle value within the outer object. In that case this accessor function does not have to be set separately, but just use the default.
# string.x([x])
If x is specified, sets the x accessor to the specified function and returns this string generator. If x is not specified, returns the current x accessor, which defaults to:
function x(d) {
return d.x;
}
The x defines the horizontal location where the inner entities are placed, typically in the center of the loom. When the string generator is used in conjunction with the loom, the resulting loom array will contain an x value within the inner object. In that case this accessor function does not have to be set separately, but just use the default.
# string.y([y])
If y is specified, sets the y accessor to the specified function and returns this string generator. If y is not specified, returns the current y accessor, which defaults to:
function y(d) {
return d.y;
}
The y defines the vertical location where the inner entities are placed. They are typically placed in a column like fashion in the center, one above the other. When the string generator is used in conjunction with the loom, the resulting loom array will contain an y value within the inner object. In that case this accessor function does not have to be set separately, but just use the default.
# string.offset([offset])
If offset is specified, sets the offset accessor to the specified function and returns this string generator. If offset is not specified, returns the current offset accessor, which defaults to:
function offset(d) {
return d.offset;
}
The offset defines the horizontal space between the inner end points of the strings, so that the text of the inner entities does not overlap the strings. It is typically set through the widthInner accessor of the loom and propagates through to the string function. Therefore, when the string generator is used in conjunction with the loom, the resulting loom array will contain an offset value within the inner object. In that case this accessor function does not have to be set separately, but just use the default.
# string.context([context])
If context is specified, sets the context and returns this string generator. If context is not specified, returns the current context, which defaults to null. If the context is not null, then the generated string is rendered to this context as a sequence of path method calls. Otherwise, a path data string representing the generated string is returned. See also d3-path.