* Jitter layout

* Fix default chart

* Add const for radius
This commit is contained in:
Ilfat Galiev 2021-04-02 17:11:54 +03:00 коммит произвёл GitHub
Родитель 3209f04234
Коммит 8268a413fc
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
11 изменённых файлов: 222 добавлений и 16 удалений

Просмотреть файл

@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<title>icons</title>
<circle cx="10" cy="4" r="2.33333" style="fill-opacity:0.0;stroke:#000;stroke-width:1px" />
<circle cx="20" cy="11" r="2.33333" style="fill-opacity:0.0;stroke:#000;stroke-width:1px" />
<circle cx="8" cy="22" r="2.33333" style="fill-opacity:0.0;stroke:#000;stroke-width:1px" />
<circle cx="23" cy="22" r="2.33333" style="fill-opacity:0.0;stroke:#000;stroke-width:1px" />
<circle cx="4" cy="12" r="2.33333" style="fill-opacity:0.0;stroke:#000;stroke-width:1px" />
<circle cx="14" cy="16" r="2.33333" style="fill-opacity:0.0;stroke:#000;stroke-width:1px" />
<circle cx="21" cy="3" r="2.33333" style="fill-opacity:0.0;stroke:#000;stroke-width:1px" />
</svg>

После

Ширина:  |  Высота:  |  Размер: 767 B

Просмотреть файл

@ -288,6 +288,10 @@ addSVGIcon(
"sublayout/packing",
require("resources/icons/icons_sublayout-packing.svg")
);
addSVGIcon(
"sublayout/jitter",
require("resources/icons/icons_sublayout-jitter.svg")
);
addSVGIcon("align/left", require("resources/icons/icons_align-left.svg"));
addSVGIcon(
"align/x-middle",

Просмотреть файл

@ -87,6 +87,10 @@ export function createDefaultPlotSegment(
gravityX: 0.1,
gravityY: 0.1,
},
jitter: {
vertical: true,
horizontal: true,
},
},
},
} as Specification.PlotSegment;

Просмотреть файл

@ -464,7 +464,7 @@ export abstract class HashMap<KeyType, ValueType> {
export class MultistringHashMap<ValueType> extends HashMap<
string[],
ValueType
> {
> {
protected separator: string = Math.random().toString(36).substr(2);
protected hash(key: string[]): string {
return key.join(this.separator);
@ -652,18 +652,18 @@ let formatOptions: FormatLocaleDefinition = {
decimal: ".",
thousands: ",",
grouping: [3],
currency: ["$", ""]
currency: ["$", ""],
};
export function getFormatOptions(): FormatLocaleDefinition {
return {
...formatOptions
...formatOptions,
};
}
export function setFormatOptions(options: FormatLocaleDefinition) {
formatOptions = {
...options
...options,
};
}
@ -674,7 +674,11 @@ export function getFormat() {
export function parseSafe(value: string, defaultValue: any = null) {
try {
return JSON.parse(value);
} catch(ex) {
} catch (ex) {
return defaultValue;
}
}
export function getRandom(startRange: number, endRange: number) {
return startRange + Math.random() * (endRange - startRange);
}

Просмотреть файл

@ -21,7 +21,7 @@ import { PlotSegmentClass } from "../plot_segment";
import { strings } from "./../../../../strings";
export interface Region2DSublayoutOptions extends Specification.AttributeMap {
type: "overlap" | "dodge-x" | "dodge-y" | "grid" | "packing";
type: "overlap" | "dodge-x" | "dodge-y" | "grid" | "packing" | "jitter";
/** Sublayout alignment (for dodge and grid) */
align: {
@ -50,6 +50,10 @@ export interface Region2DSublayoutOptions extends Specification.AttributeMap {
gravityX: number;
gravityY: number;
};
jitter: {
vertical: boolean;
horizontal: boolean;
};
}
export interface Region2DAttributes extends Specification.AttributeMap {
@ -106,6 +110,7 @@ export interface Region2DConfigurationTerminology {
/** Packing force layout */
packing: string;
overlap: string;
jitter: string;
}
export interface Region2DConfigurationIcons {
@ -119,6 +124,7 @@ export interface Region2DConfigurationIcons {
dodgeYIcon: string;
gridIcon: string;
packingIcon: string;
jitterIcon: string;
overlapIcon: string;
}
@ -232,6 +238,8 @@ export class Region2DConstraintBuilder {
public solverContext?: BuildConstraintsContext
) {}
public static defaultJitterPackingRadius = 5;
public getTableContext(): DataflowTable {
return this.plotSegment.parent.dataflow.getTable(
this.plotSegment.object.table
@ -1093,6 +1101,8 @@ export class Region2DConstraintBuilder {
if (context.mode == "x-only" || context.mode == "y-only") {
if (props.sublayout.type == "packing") {
this.sublayoutPacking(groups, context.mode == "x-only" ? "x" : "y");
} else if (props.sublayout.type == "jitter") {
this.sublayoutJitter(groups, context.mode == "x-only" ? "x" : "y");
} else {
this.fitGroups(groups, axis);
}
@ -1116,6 +1126,10 @@ export class Region2DConstraintBuilder {
if (props.sublayout.type == "packing") {
this.sublayoutPacking(groups);
}
// Jitter layout
if (props.sublayout.type == "jitter") {
this.sublayoutJitter(groups);
}
}
}
}
@ -1739,7 +1753,7 @@ export class Region2DConstraintBuilder {
}
}
if (radius == 0) {
radius = 5;
radius = Region2DConstraintBuilder.defaultJitterPackingRadius;
}
return [
solver.attr(state.attributes, "x"),
@ -1764,6 +1778,60 @@ export class Region2DConstraintBuilder {
});
}
public sublayoutJitter(groups: SublayoutGroup[], axisOnly?: "x" | "y") {
const solver = this.solver;
const state = this.plotSegment.state;
const jitterProps = this.plotSegment.object.properties.sublayout.jitter;
groups.forEach((group) => {
const markStates = group.group.map((index) => state.glyphs[index]);
const { x1, y1, x2, y2 } = group;
const points = markStates.map((state) => {
let radius = 0;
for (const e of state.marks) {
if (e.attributes.size != null) {
radius = Math.max(
radius,
Math.sqrt((e.attributes.size as number) / Math.PI)
);
} else {
const w = e.attributes.width as number;
const h = e.attributes.height as number;
if (w != null && h != null) {
radius = Math.max(radius, Math.sqrt(w * w + h * h) / 2);
}
}
}
if (radius == 0) {
radius = Region2DConstraintBuilder.defaultJitterPackingRadius;
}
return [
solver.attr(state.attributes, "x"),
solver.attr(state.attributes, "y"),
radius,
] as [Variable, Variable, number];
});
solver.addPlugin(
new ConstraintPlugins.JitterPlugin(
solver,
x1,
y1,
x2,
y2,
points,
axisOnly,
jitterProps
? jitterProps
: {
horizontal: true,
vertical: true,
}
)
);
});
}
public getHandles(): Region2DHandleDescription[] {
const state = this.plotSegment.state;
const props = this.plotSegment.object.properties;
@ -2094,6 +2162,11 @@ export class Region2DConstraintBuilder {
label: terminology.grid,
icon: icons.gridIcon,
};
const jitterOption = {
value: "jitter",
label: terminology.jitter,
icon: icons.jitterIcon,
};
const props = this.plotSegment.object.properties;
const xMode = props.xData ? props.xData.type : "null";
const yMode = props.yData ? props.yData.type : "null";
@ -2107,9 +2180,10 @@ export class Region2DConstraintBuilder {
dodgeYOption,
gridOption,
packingOption,
jitterOption,
];
}
return [overlapOption, packingOption];
return [overlapOption, packingOption, jitterOption];
}
public isSublayoutApplicable() {
@ -2275,6 +2349,24 @@ export class Region2DConstraintBuilder {
)
);
}
if (type == "jitter") {
extra.push(
m.row(
"Distribution",
m.horizontal(
[0, 1, 1],
m.inputBoolean(
{ property: "sublayout", field: ["jitter", "horizontal"] },
{ type: "highlight", icon: "sublayout/dodge-x" }
),
m.inputBoolean(
{ property: "sublayout", field: ["jitter", "vertical"] },
{ type: "highlight", icon: "sublayout/dodge-y" }
)
)
)
);
}
const options = this.applicableSublayoutOptions();
return [
m.sectionHeader("Sub-layout"),

Просмотреть файл

@ -58,6 +58,7 @@ const icons: Region2DConfigurationIcons = {
dodgeYIcon: "sublayout/dodge-y",
gridIcon: "sublayout/grid",
packingIcon: "sublayout/packing",
jitterIcon: "sublayout/jitter",
overlapIcon: "sublayout/overlap",
};
@ -86,7 +87,7 @@ export class CartesianPlotSegment extends PlotSegmentClass<
public static defaultMappingValues: Specification.AttributeMap = {};
public static defaultProperties: Specification.AttributeMap = {
public static defaultProperties: CartesianProperties = {
marginX1: 0,
marginY1: 0,
marginX2: 0,
@ -106,6 +107,15 @@ export class CartesianPlotSegment extends PlotSegmentClass<
xCount: null,
yCount: null,
},
jitter: {
horizontal: true,
vertical: true,
},
packing: {
gravityX: 0.1,
gravityY: 0.1,
},
orderReversed: null,
},
};

Просмотреть файл

@ -73,6 +73,7 @@ export let icons: Region2DConfigurationIcons = {
dodgeYIcon: "sublayout/dodge-y",
gridIcon: "sublayout/grid",
packingIcon: "sublayout/packing",
jitterIcon: "sublayout/jitter",
overlapIcon: "sublayout/overlap",
};

Просмотреть файл

@ -86,6 +86,7 @@ export let icons: Region2DConfigurationIcons = {
gridIcon: "sublayout/polar-grid",
packingIcon: "sublayout/packing",
overlapIcon: "sublayout/overlap",
jitterIcon: "sublayout/jitter",
};
export class PolarPlotSegment extends PlotSegmentClass<

Просмотреть файл

@ -3,3 +3,4 @@
export { PackingPlugin } from "./packing";
export { PolarCoordinatorPlugin } from "./polar_coordinator";
export { PolarPlotSegmentPlugin } from "./polar_plotsegment";
export { JitterPlugin } from "./jitter";

Просмотреть файл

@ -0,0 +1,76 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { forceSimulation, forceCollide, forceX, forceY } from "d3-force";
import { getRandom } from "../..";
import { ConstraintPlugin, ConstraintSolver, Variable } from "../abstract";
interface NodeType {
x?: number;
y?: number;
}
export interface JitterPluginOptions {
vertical: boolean;
horizontal: boolean;
}
export class JitterPlugin extends ConstraintPlugin {
public solver: ConstraintSolver;
public x1: Variable;
public y1: Variable;
public x2: Variable;
public y2: Variable;
public points: Array<[Variable, Variable, number]>;
public xEnable: boolean;
public yEnable: boolean;
public getXYScale: () => { x: number; y: number };
public options?: JitterPluginOptions;
constructor(
solver: ConstraintSolver,
x1: Variable,
y1: Variable,
x2: Variable,
y2: Variable,
points: Array<[Variable, Variable, number]>,
axisOnly?: "x" | "y",
options?: JitterPluginOptions
) {
super();
this.solver = solver;
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
this.points = points;
this.xEnable = axisOnly == null || axisOnly == "x";
this.yEnable = axisOnly == null || axisOnly == "y";
this.options = options;
}
public apply() {
const x1 = this.solver.getValue(this.x1);
const x2 = this.solver.getValue(this.x2);
const y1 = this.solver.getValue(this.y1);
const y2 = this.solver.getValue(this.y2);
const nodes = this.points.map((pt) => {
const x = getRandom(x1, x2);
const y = getRandom(y1, y2);
// Use forceSimulation's default initialization
return {
x,
y,
} as NodeType;
});
for (let i = 0; i < nodes.length; i++) {
if (this.options.horizontal) {
this.solver.setValue(this.points[i][0], nodes[i].x);
}
if (this.options.vertical) {
this.solver.setValue(this.points[i][1], nodes[i].y);
}
}
return true;
}
}

Просмотреть файл

@ -20,6 +20,7 @@ const cartesianTerminology: Region2DConfigurationTerminology = {
gridDirectionX: "X",
gridDirectionY: "Y",
packing: "Packing",
jitter: "Jitter",
overlap: "Overlap",
};
@ -38,6 +39,7 @@ const curveTerminology: Region2DConfigurationTerminology = {
gridDirectionX: "Tangent",
gridDirectionY: "Normal",
packing: "Packing",
jitter: "Jitter",
overlap: "Overlap",
};
@ -56,6 +58,7 @@ const polarTerminology: Region2DConfigurationTerminology = {
gridDirectionX: "Angular",
gridDirectionY: "Radial",
packing: "Packing",
jitter: "Jitter",
overlap: "Overlap",
};