chrt-dotplot
Version:
Dot Plot component for Chrt
260 lines (238 loc) • 8.04 kB
JavaScript
import {
pointSize,
pointColor,
pointStroke,
pointStrokeWidth,
pointOpacity,
strokeOpacity,
range,
rangeWidth,
rangeColor,
orient,
horizontal,
vertical,
} from './lib';
import chrtObject, { utils, cssDisplay } from 'chrt-object';
const { isNull, createSVG: create } = utils;
const DEFAULT_POINT_SIZE = 3;
const DEFAULT_POINT_COLOR = '#000';
function chrtDotPlot() {
chrtObject.call(this);
this.type = 'series';
// this.size = DEFAULT_POINT_SIZE;
// this.fill = DEFAULT_POINT_COLOR;
// this.stroke = DEFAULT_POINT_COLOR;
// this.strokeWidth = 0;
// this._opacity = 1;
this.attr('radius', DEFAULT_POINT_SIZE);
this.attr('stroke', DEFAULT_POINT_COLOR);
this.attr('fill', DEFAULT_POINT_COLOR);
this.attr('strokeWidth', 0);
this.attr('strokeOpacity', 1);
this.attr('fillOpacity', 1);
this.attr('range', false);
this.attr('rangeWidth', DEFAULT_POINT_SIZE);
this.attr('rangeColor', DEFAULT_POINT_COLOR);
horizontal.call(this);
this._classNames = ['chrt-dotplot'];
this.getXScale = () => {
if (isNull(this.fields.x)) {
this.fields.x = this.parentNode.scales.x[this.scales.x].field;
}
return this.parentNode.scales.x[this.scales.x];
};
this.draw = () => {
cssDisplay.call(this, this.attr('display')());
this.g.classList.remove(...this.g.classList)
this.g.classList.add(...this._classNames);
if (!isNull(this._data)) {
if (isNull(this.fields.x)) {
this.fields.x = this.parentNode.scales.x[this.scales.x].field;
}
if (isNull(this.fields.y)) {
//console.log('this.scales', this.scales)
//console.log('this.parentNode.scales', this.parentNode.scales)
this.fields.y = this.parentNode.scales.y[this.scales.y].field;
}
if (this.attr('range')()) {
// console.log('ORIENT', this.attr('orient')())
const rangeWidth = this.attr('rangeWidth')();
const rangeColor = this.attr('rangeColor')();
const rangeType = this.attr('orient')() === 'vertical' ? 'x' : 'y';
const ranges = this._data.reduce((acc, d) => {
const otherField = rangeType === 'x' ? this.fields.y : this.fields.x;
acc[d[this.fields[rangeType]]] = acc[d[this.fields[rangeType]]] || {
[rangeType]: d[this.fields[rangeType]],
data: [],
field: rangeType,
otherField
};
acc[d[this.fields[rangeType]]].data.push(d);
const otherData = acc[d[this.fields[rangeType]]].data.map(
d => d[otherField]
);
acc[d[this.fields[rangeType]]].max = Math.max(...otherData);
acc[d[this.fields[rangeType]]].min = Math.min(...otherData);
return acc;
}, {});
// console.log('ranges', ranges);
Object.values(ranges).forEach((r, rangeIndex) => {
const dataID = escape(`range-${r[rangeType]}-${rangeIndex}`)
let range = this.g.querySelector(
`[data-id='${dataID}']`
);
if (!range) {
range = create('line');
range.setAttribute(
'data-id',
dataID
);
this.g.appendChild(range);
}
if (
!isNull(this.parentNode.scales.x[this.scales.x]) &&
!isNull(this.parentNode.scales.y[this.scales.y])
) {
// const x = this.parentNode.scales.x[this.scales.x](range.min);
// const y = this.parentNode.scales.y[this.scales.y](d[this.fields.y]);
const coords = {
[r.field]: this.parentNode.scales[r.field][this.scales[r.field]](
r[r.field]
),
[`${r.otherField}Min`]: this.parentNode.scales[r.otherField][
this.scales[r.otherField]
](r.min),
[`${r.otherField}Max`]: this.parentNode.scales[r.otherField][
this.scales[r.otherField]
](r.max)
};
// console.log(coords);
if (rangeType === 'y') {
range.setAttribute(
'x1',
!isNaN(coords[`${r.otherField}Min`])
? coords[`${r.otherField}Min`]
: 0
);
range.setAttribute(
'x2',
!isNaN(coords[`${r.otherField}Max`])
? coords[`${r.otherField}Max`]
: 0
);
range.setAttribute(
'y1',
!isNaN(coords[r.field]) ? coords[r.field] : 0
);
range.setAttribute(
'y2',
!isNaN(coords[r.field]) ? coords[r.field] : 0
);
} else {
range.setAttribute(
'y1',
!isNaN(coords[`${r.otherField}Min`])
? coords[`${r.otherField}Min`]
: 0
);
range.setAttribute(
'y2',
!isNaN(coords[`${r.otherField}Max`])
? coords[`${r.otherField}Max`]
: 0
);
range.setAttribute(
'x1',
!isNaN(coords[r.field]) ? coords[r.field] : 0
);
range.setAttribute(
'x2',
!isNaN(coords[r.field]) ? coords[r.field] : 0
);
}
range.setAttribute('stroke-width', rangeWidth);
range.setAttribute('stroke', rangeColor);
}
});
}
this._data.forEach((d, i, arr) => {
const dataID = escape(`circle-${name}-${i}`)
let circle = this.g.querySelector(`[data-id='${dataID}']`);
if (!circle) {
circle = create('circle');
circle.setAttribute('data-id', dataID);
this.g.appendChild(circle);
}
if (
!isNull(this.parentNode.scales.x[this.scales.x]) &&
!isNull(this.parentNode.scales.y[this.scales.y])
) {
const x = this.parentNode.scales.x[this.scales.x](d[this.fields.x]);
const y = this.parentNode.scales.y[this.scales.y](d[this.fields.y]);
const cx = !isNaN(x) ? x : 0;
const cy = !isNaN(y) ? y : 0;
const r = this.attr('radius')(d, i, arr);
d.anchorPoints = {
x: cx,
width: r,
y: cy,
height: r,
relativePosition: [0,0],
directions: {
x: 1,
y: 1,
},
alignment: {
horizontal: 'middle',
vertical: 'top',
}
};
circle.setAttribute('cx', cx);
circle.setAttribute('cy', cy);
circle.setAttribute('r', r);
circle.setAttribute('fill', this.attr('fill')(d, i, arr));
circle.setAttribute(
'fill-opacity',
this.attr('fillOpacity')(d, i, arr) || 1
);
circle.setAttribute('stroke', this.attr('stroke')(d, i, arr));
circle.setAttribute(
'stroke-width',
this.attr('strokeWidth')(d, i, arr)
);
circle.setAttribute(
'stroke-opacity',
this.attr('strokeOpacity')(d, i, arr)
);
}
});
// // // console.log('points', points);
}
this.objects.forEach(obj => obj.draw());
return this.parentNode;
};
}
chrtDotPlot.prototype = Object.create(chrtObject.prototype);
chrtDotPlot.prototype.constructor = chrtDotPlot;
chrtDotPlot.parent = chrtObject.prototype;
chrtDotPlot.prototype = Object.assign(chrtDotPlot.prototype, {
pointSize,
size: pointSize,
radius: pointSize,
color: pointColor,
stroke: pointStroke,
width: pointStrokeWidth,
strokeWidth: pointStrokeWidth,
opacity: pointOpacity,
strokeOpacity,
range,
rangeColor,
rangeWidth,
orient,
horizontal,
vertical,
});
// export default chrtDotPlot;
export default function() {
return new chrtDotPlot();
}