[
{"fn": "linePath", "name": "three points", "args": [[{"x": 0, "y": 0}, {"x": 10, "y": 20}, {"x": 30, "y": 15}]], "expect": "M0,0L10,20L30,15"},
{"fn": "linePath", "name": "coordinates round to 2 places on the stored value, and -0.004 prints as 0, not -0", "args": [[{"x": 1.005, "y": 2.675}, {"x": 3.14159, "y": -0.004}]], "expect": "M1,2.67L3.14,0"},
{"fn": "linePath", "name": "negative and tie-breaking coordinates", "args": [[{"x": -1.5, "y": 2.25}, {"x": 0.125, "y": 100}]], "expect": "M-1.5,2.25L0.13,100"},
{"fn": "linePath", "name": "a null y breaks the line into two subpaths", "args": [[{"x": 0, "y": 0}, {"x": 1, "y": 1}, {"x": 2, "y": null}, {"x": 3, "y": 3}, {"x": 4, "y": 4}]], "expect": "M0,0L1,1M3,3L4,4"},
{"fn": "linePath", "name": "an isolated point is closed so a round cap shows it", "args": [[{"x": 0, "y": 0}, {"x": 1, "y": null}, {"x": 2, "y": 2}, {"x": 3, "y": null}, {"x": 4, "y": 4}, {"x": 5, "y": 5}]], "expect": "M0,0ZM2,2ZM4,4L5,5"},
{"fn": "linePath", "name": "leading and trailing gaps are dropped", "args": [[{"x": 0, "y": null}, {"x": 1, "y": 1}, {"x": 2, "y": 2}, {"x": 3, "y": null}]], "expect": "M1,1L2,2"},
{"fn": "linePath", "name": "a single point", "args": [[{"x": 1234.5, "y": 0.1}]], "expect": "M1234.5,0.1Z"},
{"fn": "linePath", "name": "no points", "args": [[]], "expect": ""},
{"fn": "linePath", "name": "only gaps", "args": [[{"x": 0, "y": null}, {"x": 1, "y": null}]], "expect": ""},
{"fn": "areaPath", "name": "an area down to a flat baseline", "args": [[{"x": 0, "y0": 100, "y1": 50}, {"x": 10, "y0": 100, "y1": 20}, {"x": 20, "y0": 100, "y1": 40}]], "expect": "M0,50L10,20L20,40L20,100L10,100L0,100Z"},
{"fn": "areaPath", "name": "a stacked band with its own baseline per point", "args": [[{"x": 0, "y0": 80, "y1": 60}, {"x": 5, "y0": 70, "y1": 50}]], "expect": "M0,60L5,50L5,70L0,80Z"},
{"fn": "areaPath", "name": "a null y1 splits the area; a lone point becomes a vertical sliver", "args": [[{"x": 0, "y0": 10, "y1": 5}, {"x": 1, "y0": 10, "y1": null}, {"x": 2, "y0": 10, "y1": 6}, {"x": 3, "y0": 10, "y1": 7}]], "expect": "M0,5L0,10ZM2,6L3,7L3,10L2,10Z"},
{"fn": "areaPath", "name": "one point", "args": [[{"x": 3, "y0": 10, "y1": 4}]], "expect": "M3,4L3,10Z"},
{"fn": "areaPath", "name": "rounding on the stored values: 0.005 up, 1.005 down, 2.675 down", "args": [[{"x": 0.005, "y0": 1.005, "y1": 2.675}, {"x": 1, "y0": 1, "y1": 1}]], "expect": "M0.01,2.67L1,1L1,1L0.01,1Z"},
{"fn": "areaPath", "name": "no points", "args": [[]], "expect": ""},
{"fn": "areaPath", "name": "only gaps", "args": [[{"x": 0, "y0": 1, "y1": null}]], "expect": ""},
{"fn": "areaPath", "name": "an area above and below a mid baseline", "args": [[{"x": 0, "y0": 50, "y1": 20}, {"x": 10, "y0": 50, "y1": 80}]], "expect": "M0,20L10,80L10,50L0,50Z"},
{"fn": "stepPath", "name": "step before: vertical first, at the previous x", "args": [[{"x": 0, "y": 0}, {"x": 10, "y": 10}, {"x": 20, "y": 5}], "before"], "expect": "M0,0L0,10L10,10L10,5L20,5"},
{"fn": "stepPath", "name": "step middle: the jump halfway between points", "args": [[{"x": 0, "y": 0}, {"x": 10, "y": 10}, {"x": 20, "y": 5}], "middle"], "expect": "M0,0L5,0L5,10L15,10L15,5L20,5"},
{"fn": "stepPath", "name": "step after: horizontal first, jump at the next x", "args": [[{"x": 0, "y": 0}, {"x": 10, "y": 10}, {"x": 20, "y": 5}], "after"], "expect": "M0,0L10,0L10,10L20,10L20,5"},
{"fn": "stepPath", "name": "two points, step before", "args": [[{"x": 0, "y": 0}, {"x": 4, "y": 8}], "before"], "expect": "M0,0L0,8L4,8"},
{"fn": "stepPath", "name": "a single point is closed", "args": [[{"x": 3, "y": 4}], "middle"], "expect": "M3,4Z"},
{"fn": "stepPath", "name": "a gap splits the steps", "args": [[{"x": 0, "y": 0}, {"x": 2, "y": 2}, {"x": 3, "y": null}, {"x": 4, "y": 1}, {"x": 8, "y": 3}], "middle"], "expect": "M0,0L1,0L1,2L2,2M4,1L6,1L6,3L8,3"},
{"fn": "stepPath", "name": "the midpoint of 0 and 1.01 is 0.505000000000000004, which rounds up", "args": [[{"x": 0, "y": 0}, {"x": 1.01, "y": 1}], "middle"], "expect": "M0,0L0.51,0L0.51,1L1.01,1"},
{"fn": "stepPath", "name": "no points", "args": [[], "after"], "expect": ""},
{"fn": "stepPath", "name": "an unknown position is an error", "args": [[{"x": 0, "y": 0}, {"x": 10, "y": 10}, {"x": 20, "y": 5}], "centre"], "expectError": "step position must be before, middle or after"},
{"fn": "monotoneCurve", "name": "a peak: the tangent at the top is flat, so the curve does not rise above 1", "args": [[{"x": 0, "y": 0}, {"x": 1, "y": 1}, {"x": 2, "y": 0}]], "expect": "M0,0C0.33,0.5,0.67,1,1,1C1.33,1,1.67,0.5,2,0"},
{"fn": "monotoneCurve", "name": "flat then rising: a Catmull-Rom curve would dip below 0 here; this one stays flat", "args": [[{"x": 0, "y": 0}, {"x": 1, "y": 0}, {"x": 2, "y": 10}]], "expect": "M0,0C0.33,0,0.67,0,1,0C1.33,0,1.67,5,2,10"},
{"fn": "monotoneCurve", "name": "four rising points, tangents limited by Fritsch-Carlson", "args": [[{"x": 0, "y": 0}, {"x": 1, "y": 2}, {"x": 2, "y": 3}, {"x": 3, "y": 6}]], "expect": "M0,0C0.33,0.75,0.67,1.5,1,2C1.33,2.5,1.67,2.33,2,3C2.33,3.67,2.67,4.83,3,6"},
{"fn": "monotoneCurve", "name": "uneven spacing", "args": [[{"x": 0, "y": 0}, {"x": 2, "y": 4}, {"x": 3, "y": 4}]], "expect": "M0,0C0.67,2,1.33,4,2,4C2.33,4,2.67,4,3,4"},
{"fn": "monotoneCurve", "name": "two points are a straight line", "args": [[{"x": 0, "y": 0}, {"x": 4, "y": 4}]], "expect": "M0,0L4,4"},
{"fn": "monotoneCurve", "name": "one point is closed", "args": [[{"x": 5, "y": 5}]], "expect": "M5,5Z"},
{"fn": "monotoneCurve", "name": "a gap starts a new curve", "args": [[{"x": 0, "y": 0}, {"x": 1, "y": 1}, {"x": 2, "y": null}, {"x": 3, "y": 0}, {"x": 4, "y": 1}, {"x": 5, "y": 0}]], "expect": "M0,0L1,1M3,0C3.33,0.5,3.67,1,4,1C4.33,1,4.67,0.5,5,0"},
{"fn": "monotoneCurve", "name": "no points", "args": [[]], "expect": ""},
{"fn": "monotoneCurve", "name": "repeated x is an error", "args": [[{"x": 0, "y": 0}, {"x": 0, "y": 1}]], "expectError": "monotone curve needs x strictly increasing"},
{"fn": "monotoneCurve", "name": "decreasing x is an error", "args": [[{"x": 2, "y": 0}, {"x": 1, "y": 1}, {"x": 0, "y": 0}]], "expectError": "monotone curve needs x strictly increasing"},
{"fn": "barRects", "name": "a vertical bar up from the baseline", "args": [[{"band": 10, "thickness": 20, "base": 100, "value": 40}], "vertical"], "expect": [{"x": 10, "y": 40, "width": 20, "height": 60}]},
{"fn": "barRects", "name": "a negative vertical bar hangs below the baseline with a positive height", "args": [[{"band": 10, "thickness": 20, "base": 100, "value": 130}], "vertical"], "expect": [{"x": 10, "y": 100, "width": 20, "height": 30}]},
{"fn": "barRects", "name": "a horizontal bar", "args": [[{"band": 5, "thickness": 10, "base": 0, "value": 75}], "horizontal"], "expect": [{"x": 0, "y": 5, "width": 75, "height": 10}]},
{"fn": "barRects", "name": "a negative horizontal bar", "args": [[{"band": 5, "thickness": 10, "base": 50, "value": 20}], "horizontal"], "expect": [{"x": 20, "y": 5, "width": 30, "height": 10}]},
{"fn": "barRects", "name": "neighbouring bars share a rounded edge (rounding each width alone leaves a gap)", "args": [[{"band": 0.004, "thickness": 10.004, "base": 0, "value": 1}, {"band": 10.008, "thickness": 10.004, "base": 0, "value": 1}], "vertical"], "expect": [{"x": 0, "y": 0, "width": 10.01, "height": 1}, {"x": 10.01, "y": 0, "width": 10, "height": 1}]},
{"fn": "barRects", "name": "a zero value is a zero-height bar", "args": [[{"band": 0, "thickness": 10, "base": 50, "value": 50}], "vertical"], "expect": [{"x": 0, "y": 50, "width": 10, "height": 0}]},
{"fn": "barRects", "name": "zero thickness", "args": [[{"band": 3, "thickness": 0, "base": 0, "value": 10}], "vertical"], "expect": [{"x": 3, "y": 0, "width": 0, "height": 10}]},
{"fn": "barRects", "name": "no bars", "args": [[], "vertical"], "expect": []},
{"fn": "barRects", "name": "a negative thickness is an error", "args": [[{"band": 0, "thickness": -1, "base": 0, "value": 10}], "vertical"], "expectError": "bar thickness must not be negative"},
{"fn": "barRects", "name": "an unknown orientation is an error", "args": [[{"band": 0, "thickness": 1, "base": 0, "value": 10}], "diagonal"], "expectError": "orientation must be vertical or horizontal"},
{"fn": "arcPath", "name": "a quarter wedge from 12 o'clock to 3 o'clock", "args": [{"cx": 50, "cy": 50, "innerRadius": 0, "outerRadius": 50, "startAngle": 0, "endAngle": 1.5707963267948966}], "expect": "M50,0A50,50,0,0,1,100,50L50,50Z"},
{"fn": "arcPath", "name": "a quarter of a donut", "args": [{"cx": 50, "cy": 50, "innerRadius": 25, "outerRadius": 50, "startAngle": 0, "endAngle": 1.5707963267948966}], "expect": "M50,0A50,50,0,0,1,100,50L75,50A25,25,0,0,0,50,25Z"},
{"fn": "arcPath", "name": "three quarters sets the large-arc flag", "args": [{"cx": 50, "cy": 50, "innerRadius": 0, "outerRadius": 50, "startAngle": 0, "endAngle": 4.71238898038469}], "expect": "M50,0A50,50,0,1,1,0,50L50,50Z"},
{"fn": "arcPath", "name": "exactly a half turn is not a large arc", "args": [{"cx": 0, "cy": 0, "innerRadius": 0, "outerRadius": 100, "startAngle": 0, "endAngle": 3.141592653589793}], "expect": "M0,-100A100,100,0,0,1,0,100L0,0Z"},
{"fn": "arcPath", "name": "anticlockwise clears the sweep flag", "args": [{"cx": 50, "cy": 50, "innerRadius": 0, "outerRadius": 50, "startAngle": 0, "endAngle": -1.5707963267948966}], "expect": "M50,0A50,50,0,0,0,0,50L50,50Z"},
{"fn": "arcPath", "name": "30 degrees: the end point rounds to 50,-86.6", "args": [{"cx": 0, "cy": 0, "innerRadius": 0, "outerRadius": 100, "startAngle": 0, "endAngle": 0.5235987755982988}], "expect": "M0,-100A100,100,0,0,1,50,-86.6L0,0Z"},
{"fn": "arcPath", "name": "a full circle is two half arcs (one arc back to its start draws nothing)", "args": [{"cx": 50, "cy": 50, "innerRadius": 0, "outerRadius": 50, "startAngle": 0, "endAngle": 6.283185307179586}], "expect": "M50,0A50,50,0,1,1,50,100A50,50,0,1,1,50,0Z"},
{"fn": "arcPath", "name": "more than a full turn is a full circle", "args": [{"cx": 50, "cy": 50, "innerRadius": 0, "outerRadius": 50, "startAngle": 0, "endAngle": 10}], "expect": "M50,0A50,50,0,1,1,50,100A50,50,0,1,1,50,0Z"},
{"fn": "arcPath", "name": "a full ring draws its hole the other way round", "args": [{"cx": 50, "cy": 50, "innerRadius": 20, "outerRadius": 50, "startAngle": 0, "endAngle": 6.283185307179586}], "expect": "M50,0A50,50,0,1,1,50,100A50,50,0,1,1,50,0ZM50,30A20,20,0,1,0,50,70A20,20,0,1,0,50,30Z"},
{"fn": "arcPath", "name": "no angle is no path", "args": [{"cx": 50, "cy": 50, "innerRadius": 0, "outerRadius": 50, "startAngle": 1, "endAngle": 1}], "expect": ""},
{"fn": "arcPath", "name": "no radius is no path", "args": [{"cx": 50, "cy": 50, "innerRadius": 0, "outerRadius": 0, "startAngle": 0, "endAngle": 1}], "expect": ""},
{"fn": "arcPath", "name": "an inner radius beyond the outer is an error", "args": [{"cx": 0, "cy": 0, "innerRadius": 60, "outerRadius": 50, "startAngle": 0, "endAngle": 1}], "expectError": "arc radii must satisfy 0 <= innerRadius <= outerRadius"},
{"fn": "arcPath", "name": "a negative inner radius is an error", "args": [{"cx": 0, "cy": 0, "innerRadius": -1, "outerRadius": 50, "startAngle": 0, "endAngle": 1}], "expectError": "arc radii must satisfy 0 <= innerRadius <= outerRadius"},
{"fn": "pieAngles", "name": "a full pie of 1, 1 and 2 in input order", "args": [[1, 1, 2], 0, 6.283185307179586, 0], "expect": [{"value": 1, "startAngle": 0, "endAngle": 1.570796}, {"value": 1, "startAngle": 1.570796, "endAngle": 3.141593}, {"value": 2, "startAngle": 3.141593, "endAngle": 6.283185}]},
{"fn": "pieAngles", "name": "a zero value has no width", "args": [[0, 3], 0, 1, 0], "expect": [{"value": 0, "startAngle": 0, "endAngle": 0}, {"value": 3, "startAngle": 0, "endAngle": 1}]},
{"fn": "pieAngles", "name": "all zeros: every slice is empty", "args": [[0, 0], 0, 6.283185307179586, 0], "expect": [{"value": 0, "startAngle": 0, "endAngle": 0}, {"value": 0, "startAngle": 0, "endAngle": 0}]},
{"fn": "pieAngles", "name": "padding: half the pad is trimmed from each side of each slice", "args": [[1, 1], 0, 2, 0.2], "expect": [{"value": 1, "startAngle": 0.1, "endAngle": 0.9}, {"value": 1, "startAngle": 1.1, "endAngle": 1.9}]},
{"fn": "pieAngles", "name": "a pad wider than the pie allows is capped at an equal share", "args": [[1, 1, 1, 1], 0, 1, 1], "expect": [{"value": 1, "startAngle": 0.125, "endAngle": 0.125}, {"value": 1, "startAngle": 0.375, "endAngle": 0.375}, {"value": 1, "startAngle": 0.625, "endAngle": 0.625}, {"value": 1, "startAngle": 0.875, "endAngle": 0.875}]},
{"fn": "pieAngles", "name": "an end before the start runs anticlockwise", "args": [[1, 1], 0, -2, 0], "expect": [{"value": 1, "startAngle": 0, "endAngle": -1}, {"value": 1, "startAngle": -1, "endAngle": -2}]},
{"fn": "pieAngles", "name": "the span is capped at one full turn", "args": [[1], 0, 10, 0], "expect": [{"value": 1, "startAngle": 0, "endAngle": 6.283185}]},
{"fn": "pieAngles", "name": "a half pie from 9 o'clock to 3 o'clock", "args": [[2, 1, 1], -1.5707963267948966, 1.5707963267948966, 0], "expect": [{"value": 2, "startAngle": -1.570796, "endAngle": 0}, {"value": 1, "startAngle": 0, "endAngle": 0.785398}, {"value": 1, "startAngle": 0.785398, "endAngle": 1.570796}]},
{"fn": "pieAngles", "name": "no values", "args": [[], 0, 6.283185307179586, 0], "expect": []},
{"fn": "pieAngles", "name": "a negative value is an error", "args": [[1, -1], 0, 6.283185307179586, 0], "expectError": "pie values must be zero or more"},
{"fn": "pieAngles", "name": "a negative pad is an error", "args": [[1], 0, 6.283185307179586, -0.1], "expectError": "padAngle must not be negative"}
]