import { isObject } from '../core.mjs';
import { select as d3_select } from '../d3.mjs';
import { getColor } from './colors.mjs';
// list of marker types which can have line widths
const root_50_67 = [2, 3, 5, 4, 25, 26, 27, 28, 30, 32, 35, 36, 37, 38, 40, 42, 44, 46],
// internal recoding of root markers
root_markers = [
0, 1, 2, 3, 4, // 0..4
5, 106, 107, 104, 1, // 5..9
1, 1, 1, 1, 1, // 10..14
1, 1, 1, 1, 1, // 15..19
104, 125, 126, 132, 4, // 20..24
25, 26, 27, 28, 130, // 25..29
30, 3, 32, 127, 128, // 30..34
35, 36, 37, 38, 137, // 35..39
40, 140, 42, 142, 44, // 40..44
144, 46, 146, 148, 149]; // 45..49
/**
* @summary Handle for marker attributes
* @private
*/
class TAttMarkerHandler {
/** @summary constructor
* @param {object} args - attributes, see {@link TAttMarkerHandler#setArgs} for details */
constructor(args) {
this.x0 = this.y0 = 0;
this.color = 'black';
this.style = 1;
this.size = 8;
this.scale = 1;
this.stroke = true;
this.fill = true;
this.marker = '';
this.ndig = 0;
this.used = true;
this.changed = false;
this.func = this.apply.bind(this);
this.setArgs(args);
this.changed = false;
}
/** @summary Set marker attributes.
* @param {object} args - arguments can be
* @param {object} args.attr - instance of TAttrMarker (or derived class) or
* @param {string} args.color - color in HTML form like grb(1,4,5) or 'green'
* @param {number} args.style - marker style
* @param {number} args.size - marker size
* @param {number} [args.refsize] - when specified and marker size < 1, marker size will be calculated relative to that size */
setArgs(args) {
if (isObject(args) && (typeof args.fMarkerStyle === 'number')) args = { attr: args };
if (args.attr) {
if (args.color === undefined)
args.color = args.painter ? args.painter.getColor(args.attr.fMarkerColor) : getColor(args.attr.fMarkerColor);
if (!args.style || (args.style < 0)) args.style = args.attr.fMarkerStyle;
if (!args.size) args.size = args.attr.fMarkerSize;
}
this.color = args.color;
this.style = args.style;
this.size = args.size;
this.refsize = args.refsize;
this._configure();
}
/** @summary Set usage flag of attribute */
setUsed(flag) {
this.used = flag;
}
/** @summary Reset position, used for optimization of drawing of multiple markers
* @private */
resetPos() { this.lastx = this.lasty = null; }
/** @summary Create marker path for given position.
* @desc When drawing many elementary points, created path may depend from previously produced markers.
* @param {number} x - first coordinate
* @param {number} y - second coordinate
* @return {string} path string */
create(x, y) {
if (!this.optimized)
return `M${(x + this.x0).toFixed(this.ndig)},${(y + this.y0).toFixed(this.ndig)}${this.marker}`;
// use optimized handling with relative position
const xx = Math.round(x), yy = Math.round(y);
let mv = `M${xx},${yy}`;
if (this.lastx !== null) {
if ((xx === this.lastx) && (yy === this.lasty))
mv = ''; // pathological case, but let exclude it
else {
const m2 = `m${xx-this.lastx},${yy - this.lasty}`;
if (m2.length < mv.length) mv = m2;
}
}
this.lastx = xx + 1; this.lasty = yy;
return mv + 'h1';
}
/** @summary Returns full size of marker */
getFullSize() { return this.scale * this.size; }
/** @summary Returns approximate length of produced marker string */
getMarkerLength() { return this.marker ? this.marker.length : 10; }
/** @summary Change marker attributes.
* @param {string} color - marker color
* @param {number} style - marker style
* @param {number} size - marker size */
change(color, style, size) {
this.changed = true;
if (color !== undefined) this.color = color;
if ((style !== undefined) && (style >= 0)) this.style = style;
if (size !== undefined) this.size = size;
this._configure();
}
/** @summary Prepare object to create marker
* @private */
_configure() {
this.x0 = this.y0 = 0;
if ((this.style === 1) || (this.style === 777)) {
this.fill = false;
this.marker = 'h1';
this.size = 1;
this.optimized = true;
this.resetPos();
return true;
}
this.optimized = false;
this.lwidth = 1;
let style = this.style;
if (style >= 50) {
this.lwidth = 2 + Math.floor((style - 50) / root_50_67.length);
style = root_50_67[(style - 50) % root_50_67.length];
}
const marker_kind = root_markers[style] ?? 104,
shape = marker_kind % 100;
this.fill = (marker_kind >= 100);
this.scale = this.refsize || 8; // v7 defines refsize as 1 or pad height
const size = this.getFullSize();
this.ndig = (size > 7) ? 0 : ((size > 2) ? 1 : 2);
if (shape === 30) this.ndig++; // increase precision for star
let s1 = size.toFixed(this.ndig);
const s2 = (size/2).toFixed(this.ndig),
s3 = (size/3).toFixed(this.ndig),
s4 = (size/4).toFixed(this.ndig),
s8 = (size/8).toFixed(this.ndig),
s38 = (size*3/8).toFixed(this.ndig),
s34 = (size*3/4).toFixed(this.ndig);
switch (shape) {
case 1: // dot
this.marker = 'h1';
break;
case 2: // plus
this.y0 = -size / 2;
this.marker = `v${s1}m-${s2},-${s2}h${s1}`;
break;
case 3: // asterisk
this.y0 = -size / 2;
this.marker = `v${s1}m-${s2},-${s2}h${s1}m-${s8},-${s38}l-${s34},${s34}m${s34},0l-${s34},-${s34}`;
break;
case 4: // circle
this.x0 = -parseFloat(s2);
s1 = (parseFloat(s2) * 2).toFixed(this.ndig);
this.marker = `a${s2},${s2},0,1,0,${s1},0a${s2},${s2},0,1,0,-${s1},0z`;
break;
case 5: // multiply
this.x0 = this.y0 = -3 / 8 * size;
this.marker = `l${s34},${s34}m0,-${s34}l-${s34},${s34}`;
break;
case 6: // small dot
this.x0 = -1;
this.marker = 'a1,1,0,1,0,2,0a1,1,0,1,0,-2,0z';
break;
case 7: // medium dot
this.x0 = -1.5;
this.marker = 'a1.5,1.5,0,1,0,3,0a1.5,1.5,0,1,0,-3,0z';
break;
case 25: // square
this.x0 = this.y0 = -size / 2;
this.marker = `v${s1}h${s1}v-${s1}z`;
break;
case 26: // triangle-up
this.y0 = -size / 2;
this.marker = `l-${s2},${s1}h${s1}z`;
break;
case 27: // diamond
this.y0 = -size / 2;
this.marker = `l${s3},${s2}l-${s3},${s2}l-${s3},-${s2}z`;
break;
case 28: // cross
this.x0 = this.y0 = size / 6;
this.marker = `h${s3}v-${s3}h-${s3}v-${s3}h-${s3}v${s3}h-${s3}v${s3}h${s3}v${s3}h${s3}z`;
break;
case 30: { // star
this.y0 = -size / 2;
const s56 = (size*5/6).toFixed(this.ndig), s58 = (size*5/8).toFixed(this.ndig);
this.marker = `l${s3},${s1}l-${s56},-${s58}h${s1}l-${s56},${s58}z`;
break;
}
case 32: // triangle-down
this.y0 = size / 2;
this.marker = `l-${s2},-${s1}h${s1}z`;
break;
case 35:
this.x0 = -size / 2;
this.marker = `l${s2},${s2}l${s2},-${s2}l-${s2},-${s2}zh${s1}m-${s2},-${s2}v${s1}`;
break;
case 36:
this.x0 = this.y0 = -size / 2;
this.marker = `h${s1}v${s1}h-${s1}zl${s1},${s1}m0,-${s1}l-${s1},${s1}`;
break;
case 37:
this.x0 = -size/2;
this.marker = `h${s1}l-${s4},-${s2}l-${s2},${s1}h${s2}l-${s2},-${s1}z`;
break;
case 38:
this.x0 = -size/4; this.y0 = -size/2;
this.marker = `h${s2}l${s4},${s4}v${s2}l-${s4},${s4}h-${s2}l-${s4},-${s4}v-${s2}zm${s4},0v${s1}m-${s2},-${s2}h${s1}`;
break;
case 40:
this.x0 = -size/4; this.y0 = -size/2;
this.marker = `l${s2},${s1}l${s4},-${s4}l-${s1},-${s2}zm${s2},0l-${s2},${s1}l-${s4},-${s4}l${s1},-${s2}z`;
break;
case 42:
this.y0 = -size/2;
this.marker = `l${s8},${s38}l${s38},${s8}l-${s38},${s8}l-${s8},${s38}l-${s8},-${s38}l-${s38},-${s8}l${s38},-${s8}z`;
break;
case 44:
this.x0 = -size/4; this.y0 = -size/2;
this.marker = `h${s2}l-${s8},${s38}l${s38},-${s8}v${s2}l-${s38},-${s8}l${s8},${s38}h-${s2}l${s8},-${s38}l-${s38},${s8}v-${s2}l${s38},${s8}z`;
break;
case 46:
this.x0 = -size/4; this.y0 = -size/2;
this.marker = `l${s4},${s4}l${s4},-${s4}l${s4},${s4}l-${s4},${s4}l${s4},${s4}l-${s4},${s4}l-${s4},-${s4}l-${s4},${s4}l-${s4},-${s4}l${s4},-${s4}l-${s4},-${s4}z`;
break;
case 48:
this.x0 = -size/4; this.y0 = -size/2;
this.marker = `l${s4},${s4}l-${s4},${s4}l-${s4},-${s4}zm${s2},0l${s4},${s4}l-${s4},${s4}l-${s4},-${s4}zm0,${s2}l${s4},${s4}l-${s4},${s4}l-${s4},-${s4}zm-${s2},0l${s4},${s4}l-${s4},${s4}l-${s4},-${s4}z`;
break;
case 49:
this.x0 = -size/6; this.y0 = -size/2;
this.marker = `h${s3}v${s3}h-${s3}zm${s3},${s3}h${s3}v${s3}h-${s3}zm-${s3},${s3}h${s3}v${s3}h-${s3}zm-${s3},-${s3}h${s3}v${s3}h-${s3}z`;
break;
default: // diamond
this.y0 = -size / 2;
this.marker = `l${s3},${s2}l-${s3},${s2}l-${s3},-${s2}z`;
break;
}
return true;
}
/** @summary get stroke color */
getStrokeColor() { return this.stroke ? this.color : 'none'; }
/** @summary get fill color */
getFillColor() { return this.fill ? this.color : 'none'; }
/** @summary returns true if marker attributes will produce empty (invisible) output */
empty() { return (this.color === 'none') || (!this.fill && !this.stroke); }
/** @summary Apply marker styles to created element */
apply(selection) {
this.used = true;
selection.style('stroke', this.stroke ? this.color : 'none')
.style('stroke-width', this.stroke && (this.lwidth > 1) ? this.lwidth : null)
.style('fill', this.fill ? this.color : 'none');
}
/** @summary Method used when color or pattern were changed with OpenUi5 widgets.
* @private */
verifyDirectChange(/* painter */) {
this.change(this.color, parseInt(this.style), parseFloat(this.size));
}
/** @summary Create sample with marker in given SVG element
* @param {selection} svg - SVG element
* @param {number} width - width of sample SVG
* @param {number} height - height of sample SVG
* @private */
createSample(svg, width, height, plain) {
if (plain) svg = d3_select(svg);
this.resetPos();
svg.append('path')
.attr('d', this.create(width / 2, height / 2))
.call(this.func);
}
} // class TAttMarkerHandler
export { TAttMarkerHandler };