New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@flourish/chart-layout

Package Overview
Dependencies
Maintainers
11
Versions
66
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@flourish/chart-layout - npm Package Compare versions

Comparing version 2.0.1 to 2.1.0-prerelease.1

2

package.json
{
"name": "@flourish/chart-layout",
"version": "2.0.1",
"version": "2.1.0-prerelease.1",
"description": "Create axes",

@@ -5,0 +5,0 @@ "main": "chart-layout.js",

@@ -151,2 +151,9 @@ # Flourish chart layout

### `chart_layout.xTicks.autoLabelSpace([value, [unit]])`<br/>`chart_layout.yTicks.autoLabelSpace([value, [unit]])`<br/>`chart_layout.y2Ticks.autoLabelSpace([value, [unit]])`
All three `*Ticks` methods outlined above have a sub-method that can be used to define how the maximum space for tick labels is calculated when the Flourish-user specifies the "auto" option for the relevant `tick_label_space_mode` property. These methods recognise up to two parameters. `value` specifies the magnitude of the margin in the given `unit`. Recognised units are "px", "rem" and "fraction". In the case of "fraction", this refers to the height (for the x method) or width of the chart-layout instance. If only a `value` is specified when calling one of these methods, the `unit` is assumed to be "px". The `instance` is returned in either case. If `value` is not specified when calling one of these functions then an object is returned that includes the current choice of `unit` and the calculated values for all three options based on the currently specified `value` and `unit` combination.
The space being defined is a height for the x axis and a width for the y and y2 axes. Labels that are too long for the defined space may be automatically shorted (with trailing characters replaced by a single "…" character). However, if the labels are oriented at right angles to the relevant spatial dimension, no shortening will occur.
## Other methods of `chart_layout`

@@ -153,0 +160,0 @@

@@ -0,1 +1,8 @@

# 2.1.0 (prerelease)
* Add label space settings and tick-label cropping
* Rotated labels on y-axes
* Better tick visibility settings
* Better transitions between linear and log scales
* Better setting visibility
# 2.0.1

@@ -2,0 +9,0 @@ * Fix global option for scales

@@ -72,2 +72,64 @@ var remToPx;

export { updateRemToPx, remToPx, getFont, xyToTranslate, angleToRotate, linesIntersect, log10, getExponent, getSignificantDigitCount };
function sign(val) {
if (!val || typeof val !== "number") return 0;
return val > 0 ? 1 : -1;
}
// The safeScale function wraps a standard d3-scale, preventing it from returning a NaN value or +-Infinity
// which can lead to errors when being used for translations during transitions - eg from a linear scale to
// a log scale or vice versa
function safeScale(scale) {
var type = scale.type; // Store the type since scale.copy won't copy that custom property across
scale = scale.copy(); // Use a copy of the scale so it can't be tampered with from outside
scale.type = type; // Add the type information to the new scale
var SAFE_POSITION = -1e6; // The +-1e6th pixel should not be on screen
var COPIED_METHODS = ["domain", "range"];
var scaleWrapper = function(unscaled_value) {
var val = scale(unscaled_value);
// Catch values that will break SVG transforms
if (isNaN(val) || Math.abs(val) === Infinity) {
// Work out whether should be off top/left (flipped === false) or bottom/right (flipped)
var flipped;
var range = scale.range();
if (type !== "numeric") flipped = range[0] > range[1] ? true : false;
else {
var domain = scale.domain();
// flipped true for ordinary y axis or x axis is set so go largest -> smallest
flipped = sign(domain[1] - domain[0]) !== sign(range[1] - range[0]);
}
val = SAFE_POSITION * (flipped ? -1 : 1);
}
return val;
};
// Having the range and domain easily accessible is useful, so apply these functions
COPIED_METHODS.forEach(function(name) {
scaleWrapper[name] = function() {
var result = scale[name].apply(scale, arguments);
// Return the wrapper rather than the wrapped scale object
return result === scale ? scaleWrapper : result;
};
});
// Add a custom copy method and the scale property to the wrapper
scaleWrapper.copy = function() { return safeScale(scale); };
scaleWrapper.type = scale.type;
return scaleWrapper;
}
export {
updateRemToPx,
remToPx,
getFont,
xyToTranslate,
angleToRotate,
linesIntersect,
log10,
getExponent,
getSignificantDigitCount,
safeScale
};

@@ -62,3 +62,2 @@ import { scaleLinear, scaleLog, scalePoint } from "d3-scale";

var scale = scaleLog().domain(extent).nice();
scale.type = "numeric";
var domain = scale.domain();

@@ -70,4 +69,6 @@

if (max > 0) domain[1] = max;
scale.domain(domain);
scale.type = "numeric";
return scale.domain(domain);
return scale;
}

@@ -74,0 +75,0 @@

@@ -24,2 +24,4 @@ var X_DEFAULTS = Object.freeze({

tick_label_weight: "normal",
tick_label_space_mode: "auto",
tick_label_space: 10,

@@ -26,0 +28,0 @@ gridlines_visible: false,

@@ -26,2 +26,4 @@ import { fillInDefaults } from "./common";

tick_label_weight: "normal",
tick_label_space_mode: "auto",
tick_label_space: 10,

@@ -28,0 +30,0 @@ gridlines_visible: true,

import { select } from "d3-selection";
import { remToPx, getFont, getSignificantDigitCount, getExponent } from "../common";
var DUMMY_TEXT = "Testing";
function degToRad(deg) {
return deg * (Math.PI / 180);
}
function getFitTextFunction(params) {
var max_space = params.max_space !== undefined ? params.max_space : 100;
var text_height = params.text_height !== undefined ? params.text_height : remToPx(1);
var angle = params.angle || 0;
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
ctx.font = params.font;
var max_width = max_space;
if (angle === 90) max_width = Infinity; // No point stripping letters off if angle is 90 degrees
else if (angle) {
var theta = degToRad(angle);
max_width = (max_space - text_height * Math.sin(theta)) / Math.cos(theta);
}
return function(text) {
var remove_counter = 0;
var new_text, width;
do {
new_text = remove_counter ? text.substring(0, text.length - remove_counter) + "…" : text;
width = ctx.measureText(new_text).width;
}
while ((width > max_width) && (++remove_counter < text.length));
return { text: new_text, text_width: width };
};
}
function getAutoLabelSpaceFunction(instance, dimension) {
var UNITS = [ "px", "rem", "fraction" ];
dimension = dimension || "width";
var value = 0.3;
var unit = "fraction";
var getValues = function() {
var dim = instance[dimension]();
var px = unit === "px" ? value : (unit === "rem" ? remToPx(value) : (value * dim));
return {
px: px,
rem: unit === "rem" ? value : px / remToPx(1),
fraction: unit === "fraction" ? value : px / dim,
unit: unit
};
};
return function(v, u) {
if (v === undefined) return getValues();
value = Math.max(v, 0);
unit = UNITS.indexOf(u) !== -1 ? u : "px";
return instance;
};
}
function initXTicks(instance, state) {

@@ -14,18 +75,24 @@ var x = state.x;

var angle = +x.tick_label_angle;
var theta = angle * Math.PI/180;
var sinTheta = Math.sin(theta);
var cosTheta = Math.cos(theta);
var max_box_height = 0;
var format = instance.xFormat();
var text_height;
var text_selection = group.append("text")
group.append("text")
.style("opacity", 0)
.style("font", font);
.style("font", font)
.each(function() {
var bounds = select(this).text(DUMMY_TEXT).node().getBoundingClientRect();
text_height = bounds.height;
})
.remove();
var max_space = remToPx(x.tick_label_space);
if (x.tick_label_space_mode === "auto") max_space = instance.xTicks.autoLabelSpace().px;
var params = { text_height: text_height, max_space: max_space, font: font, angle: 90 - angle };
var fitText = getFitTextFunction(params);
ticks = tick_array.map(function(value, index) {
var text = format(value);
text_selection.text(text);
var bounds = text_selection.node().getBoundingClientRect();
var text_width = bounds.width;
var text_height = bounds.height;
var result = fitText(format(value));
var text = result.text;
var text_width = result.text_width;
var box_width, box_height, box_width_left, box_width_right, box_height_left, box_height_right;

@@ -47,6 +114,9 @@

*/
var theta = degToRad(angle);
var sinTheta = Math.sin(theta);
var cosTheta = Math.cos(theta);
box_width_left = text_width * cosTheta; // horizontal distance between tl and tr after rotation
box_width_right = text_height * sinTheta; // horizontal distance between br and tr after rotation
box_height_left = text_height * sinTheta; // vertical distance between tl and tr after rotation
box_height_right = text_width * cosTheta; // vertical distance between br and tr after rotation
box_height_left = text_width * sinTheta; // vertical distance between tl and tr after rotation
box_height_right = text_height * cosTheta; // vertical distance between br and tr after rotation
box_width = box_width_left + box_width_right; // total bounding box width after rotation

@@ -64,3 +134,3 @@ box_height = box_height_left + box_height_right; // total bounding box height after rotation

box_height_left = box_height_right;
box_height_right = box_height_left;
box_height_right = temp;
}

@@ -89,6 +159,4 @@

text_selection.remove();
ticks.type = type;
ticks.max_box_height = max_box_height;
ticks.max_box_height = x.tick_label_space_mode === "fixed" ? remToPx(x.tick_label_space) : max_box_height;
Object.freeze(ticks);

@@ -99,7 +167,5 @@

var getTicks = function() {
return ticks;
};
var getTicks = function() { return ticks; };
getTicks._update = setTicks;
getTicks.autoLabelSpace = getAutoLabelSpaceFunction(instance, "height");

@@ -110,3 +176,7 @@ return getTicks;

function initVerticalTicks(instance, y, y_data, y_format) {
function initVerticalTicks(instance, state, y_name) {
var y = state[y_name];
var data_name = y_name + "Data";
var format_name = y_name + "Format";
var ticks_name = y_name + "Ticks";
var ticks = Object.freeze([]);

@@ -116,27 +186,33 @@ var group = instance.chart.select(".fl-y-axes");

var setTicks = function(tick_array) {
var type = instance[y_data]().string_array ? "string" : "numeric";
var type = instance[data_name]().string_array ? "string" : "numeric";
var font = getFont(select(".fl-y-axes"), y.tick_label_size, y.tick_label_weight);
var angle = +y.tick_label_angle;
var theta = angle * Math.PI/180;
var sinTheta = Math.sin(theta);
var cosTheta = Math.cos(theta);
var max_box_width = 0;
var format = instance[y_format]();
var format = instance[format_name]();
var text_height;
var text_selection = group.append("text")
group.append("text")
.style("opacity", 0)
.style("font", font);
.style("font", font)
.each(function() {
var bounds = select(this).text(DUMMY_TEXT).node().getBoundingClientRect();
text_height = bounds.height;
})
.remove();
var max_space = remToPx(y.tick_label_space);
if (y.tick_label_space_mode === "auto") max_space = instance[ticks_name].autoLabelSpace().px;
var params = { text_height: text_height, max_space: max_space, font: font, angle: angle };
var fitText = getFitTextFunction(params);
ticks = tick_array.map(function(value, index) {
var text = format(value);
text_selection.text(text);
var bounds = text_selection.node().getBoundingClientRect();
var text_width = bounds.width;
var text_height = bounds.height;
var result = fitText(format(value));
var text = result.text;
var text_width = result.text_width;
var box_width, box_height, box_width_above, box_width_below, box_height_above, box_height_below;
if (angle === 0 || angle === 90) {
box_width = angle === 0 ? text_width : text_height;
box_width = !angle ? text_width : text_height;
// Add extra padding for text rotated by 90 degrees
box_height = angle === 0 ? text_height : text_width + remToPx(0.5);
box_height = !angle ? text_height : text_width + remToPx(0.5);
box_width_above = box_width;

@@ -152,4 +228,7 @@ box_width_below = box_width;

*/
box_width_above = text_width * sinTheta; // horizontal distance between tr and br after rotation
box_width_below = text_height * cosTheta; // horizontal distance between bl and br after rotation
var theta = degToRad(angle);
var sinTheta = Math.sin(theta);
var cosTheta = Math.cos(theta);
box_width_above = text_height * sinTheta; // horizontal distance between tr and br after rotation
box_width_below = text_width * cosTheta; // horizontal distance between bl and br after rotation
box_height_above = text_height * cosTheta; // vertical distance between tr and br after rotation

@@ -183,6 +262,4 @@ box_height_below = text_width * sinTheta; // vertical distance between bl and br after rotation

text_selection.remove();
ticks.type = type;
ticks.max_box_width = max_box_width;
ticks.max_box_width = y.tick_label_space_mode === "fixed" ? remToPx(y.tick_label_space) : max_box_width;
Object.freeze(ticks);

@@ -193,7 +270,5 @@

var getTicks = function() {
return ticks;
};
var getTicks = function() { return ticks; };
getTicks._update = setTicks;
getTicks.autoLabelSpace = getAutoLabelSpaceFunction(instance);

@@ -205,9 +280,9 @@ return getTicks;

function initYTicks(instance, state) {
return initVerticalTicks(instance, state.y, "yData", "yFormat");
return initVerticalTicks(instance, state, "y");
}
function initY2Ticks(instance, state) {
return initVerticalTicks(instance, state.y2, "y2Data", "y2Format");
return initVerticalTicks(instance, state, "y2");
}
export { initXTicks, initYTicks, initY2Ticks };

@@ -8,4 +8,7 @@ function tickSorter(a, b) {

if (diff) return diff;
// Then, finally, favour the smallest number
diff = a.index - b.index;
// Then the number with the largest absolute value
diff = Math.abs(b.value) - Math.abs(a.value);
if (diff) return diff;
// Then, finally, favour the largest number
diff = b.value - a.value;
return diff;

@@ -12,0 +15,0 @@ }

import { select } from "d3-selection";
import { remToPx, xyToTranslate, angleToRotate, linesIntersect } from "../../common";
import { remToPx, xyToTranslate, angleToRotate, linesIntersect, safeScale } from "../../common";
import { tickSorter, userSelectNone } from "./common";

@@ -15,4 +15,4 @@

function setValues() {
xScale = instance.xScale();
yScale = instance.yScale();
xScale = safeScale(instance.xScale());
yScale = safeScale(instance.yScale());
animation_duration = oldXScale ? instance.animationDuration() : 0;

@@ -122,8 +122,3 @@ var range = xScale.range();

.duration(animation_duration)
.attr("transform", function(d) {
var x = exitingXScale(d.value);
// Prevent errors being thrown when x is +/- Infinity
if (Math.abs(x) > 1e6) x = 1e6 * (x > 0 ? 1 : -1);
return xyToTranslate(x, axis_y_position);
})
.attr("transform", function(d) { return xyToTranslate(exitingXScale(d.value), axis_y_position); })
.style("opacity", 0)

@@ -340,8 +335,3 @@ .remove();

.duration(animation_duration)
.attr("transform", function(d) {
var x = exitingXScale(d.value);
// Prevent errors being thrown when x is +/- Infinity
if (Math.abs(x) > 1e6) x = 1e6 * (x > 0 ? 1 : -1);
return xyToTranslate(x, axis_y_position);
})
.attr("transform", function(d) { return xyToTranslate(exitingXScale(d.value), axis_y_position); })
.style("opacity", 0)

@@ -409,4 +399,4 @@ .remove();

var p1q1 = [ x_anchor, y_anchor ];
var p2 = [ x_anchor + d.box_width_right, y_anchor + d.box_height_right];
var q2 = [ x_anchor - d.box_width_left, y_anchor + d.box_height_left];
var p2 = [ x_anchor - d.box_width_left, y_anchor - d.box_height_left];
var q2 = [ x_anchor + d.box_width_right, y_anchor - d.box_height_right];
var p = [ p1q1, p2 ];

@@ -417,6 +407,6 @@ var q = [ p1q1, q2 ];

if (x_anchor <= placed_tick.x_anchor) {
if (linesIntersect(p, placed_tick.q)) return 0;
if (linesIntersect(q, placed_tick.p)) return 0;
}
else {
if (linesIntersect(placed_tick.p, q)) return 0; // eslint-disable-line no-lonely-if
if (linesIntersect(p, placed_tick.q)) return 0; // eslint-disable-line no-lonely-if
}

@@ -423,0 +413,0 @@ }

import { select } from "d3-selection";
import { remToPx, xyToTranslate, angleToRotate, linesIntersect } from "../../common";
import { remToPx, xyToTranslate, angleToRotate, linesIntersect, safeScale } from "../../common";
import { tickSorter, userSelectNone } from "./common";

@@ -16,4 +16,4 @@

return function() {
var xScale = instance.xScale();
var yScale = instance.yScale();
var xScale = safeScale(instance.xScale());
var yScale = safeScale(instance.yScale());
var ticks = instance.yTicks();

@@ -95,8 +95,3 @@ var animation_duration = oldXScale ? instance.animationDuration() : 0;

.duration(animation_duration)
.attr("transform", function(d) {
var y = exitingYScale(d.value);
// Prevent errors being thrown when y is +/- Infinity
if (Math.abs(y) > 1e6) y = 1e6 * (y > 0 ? 1 : -1);
return xyToTranslate(x0, y);
})
.attr("transform", function(d) { return xyToTranslate(x0, exitingYScale(d.value)); })
.style("opacity", 0)

@@ -103,0 +98,0 @@ .remove();

import { select } from "d3-selection";
import { remToPx, xyToTranslate, angleToRotate, linesIntersect } from "../../common";
import { remToPx, xyToTranslate, angleToRotate, linesIntersect, safeScale } from "../../common";
import { tickSorter, userSelectNone } from "./common";

@@ -16,4 +16,4 @@

return function() {
var xScale = instance.xScale();
var yScale = instance.y2Scale();
var xScale = safeScale(instance.xScale());
var yScale = safeScale(instance.y2Scale());
var ticks = instance.y2Ticks();

@@ -96,8 +96,3 @@ var animation_duration = oldXScale ? instance.animationDuration() : 0;

.duration(animation_duration)
.attr("transform", function(d) {
var y = exitingYScale(d.value);
// Prevent errors being thrown when y is +/- Infinity
if (Math.abs(y) > 1e6) y = 1e6 * (y > 0 ? 1 : -1);
return xyToTranslate(x1, y);
})
.attr("transform", function(d) { return xyToTranslate(x1, exitingYScale(d.value)); })
.style("opacity", 0)

@@ -104,0 +99,0 @@ .remove();

@@ -1,2 +0,2 @@

import { remToPx } from "../../common";
import { remToPx, safeScale } from "../../common";

@@ -26,4 +26,4 @@ var DASH_ARRAYS = {

return function() {
var xScale = instance.xScale();
var yScale = instance.yScale();
var xScale = safeScale(instance.xScale());
var yScale = safeScale(instance.yScale());
oldXScale = oldXScale || xScale;

@@ -30,0 +30,0 @@ oldYScale = oldYScale || yScale;

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc