Uses class

This commit is contained in:
Roman Galochkin 2020-02-18 14:29:51 +03:00
parent 5c9f840e14
commit 6b3d2f0c8b
2 changed files with 212 additions and 153 deletions

View File

@ -46,7 +46,8 @@ function CdfChart(props) {
.verticalLine(props.verticalLine) .verticalLine(props.verticalLine)
.showVerticalLine(props.showVerticalLine) .showVerticalLine(props.showVerticalLine)
.container("#" + id) .container("#" + id)
.data({ primary: props.primaryDistribution }).render(); .data({ primary: props.primaryDistribution })
.render();
}); });
const style = !!props.width ? { width: props.width + "px" } : {}; const style = !!props.width ? { width: props.width + "px" } : {};

View File

@ -1,37 +1,156 @@
import * as d3 from 'd3'; const d3 = require('d3');
const moment = require('moment');
function chart() { const format = ts => moment(ts).format("dddd, MMMM Do YYYY, h:mm:ss a");
// Id for event handlings.
const attrs = {
id: 'ID' + Math.floor(Math.random() * 1000000),
svgWidth: 400,
svgHeight: 400,
marginTop: 5, d3.selection.prototype.patternify = function patternify(params) {
marginBottom: 5, const selector = params.selector;
marginRight: 50, const elementTag = params.tag;
marginLeft: 5, const data = params.data || [selector];
container: 'body', // Pattern in action.
minX: false, const selection = this.selectAll('.' + selector).data(data, (d, i) => {
maxX: false, if (typeof d === 'object' && d.id) {
scale: 'linear', return d.id;
showDistributionLines: true,
areaColors: ['#E1E5EC', '#E1E5EC'],
logBase: 10,
verticalLine: 110,
showVerticalLine: true,
data: null,
onHover: (e) => {
},
};
function main() {
const container = d3.select(attrs.container);
if (container.node() === null) {
return;
} }
return i;
});
selection.exit().remove();
return selection
.enter()
.append(elementTag)
.merge(selection)
.attr('class', selector);
};
class Chartigo {
constructor() {
this.attrs = {
svgWidth: 400,
svgHeight: 400,
marginTop: 5,
marginBottom: 5,
marginRight: 50,
marginLeft: 5,
container: 'body',
minX: false,
maxX: false,
scale: 'linear',
showDistributionLines: true,
areaColors: ['#E1E5EC', '#E1E5EC'],
logBase: 10,
verticalLine: 110,
showVerticalLine: true,
data: null,
onHover: (e) => {
},
};
this.hoverLine = null;
this.xScale = null;
this.dataPoints = null;
this.mouseover = this.mouseover.bind(this);
this.mouseout = this.mouseout.bind(this);
}
svgWidth(svgWidth) {
this.attrs.svgWidth = svgWidth;
return this;
}
svgHeight(height) {
this.attrs.svgHeight = height;
return this;
}
maxX(maxX) {
this.attrs.maxX = maxX;
return this;
}
minX(minX) {
this.attrs.minX = minX;
return this;
}
onHover(onHover) {
this.attrs.onHover = onHover;
return this;
}
marginBottom(marginBottom) {
this.attrs.marginBottom = marginBottom;
return this;
}
marginLeft(marginLeft) {
this.attrs.marginLeft = marginLeft;
return this;
}
marginRight(marginRight) {
this.attrs.marginRight = marginRight;
return this;
}
marginTop(marginTop) {
this.attrs.marginTop = marginTop;
return this;
}
showDistributionLines(showDistributionLines) {
this.attrs.showDistributionLines = showDistributionLines;
return this;
}
verticalLine(verticalLine) {
this.attrs.verticalLine = verticalLine;
return this;
}
showVerticalLine(showVerticalLine) {
this.attrs.showVerticalLine = showVerticalLine;
return this;
}
container(container) {
this.attrs.container = container;
return this;
}
data(data) {
this.attrs.data = data;
return this;
}
/**
* @param key
* @returns {[]}
*/
getDatapoints(key) {
const dt = [];
const data = this.attrs.data[key];
const len = data.xs.length;
for (let i = 0; i < len; i++) {
dt.push({
x: data.xs[i],
y: data.ys[i]
})
}
return dt;
}
render() {
const attrs = this.attrs;
const container = d3.select(attrs.container);
if (container.node() === null) return;
const containerRect = container.node().getBoundingClientRect(); const containerRect = container.node().getBoundingClientRect();
if (containerRect.width > 0) { if (containerRect.width > 0) {
@ -49,7 +168,7 @@ function chart() {
const areaColor = d3.scaleOrdinal().range(attrs.areaColors); const areaColor = d3.scaleOrdinal().range(attrs.areaColors);
const dataPoints = [getDatapoints('primary')]; this.dataPoints = [this.getDatapoints('primary')];
// Scales. // Scales.
let xScale; let xScale;
@ -70,6 +189,7 @@ function chart() {
.domain([attrs.minX, attrs.maxX]) .domain([attrs.minX, attrs.maxX])
.range([0, calc.chartWidth]); .range([0, calc.chartWidth]);
} }
this.xScale = xScale;
const yMin = d3.min(attrs.data.primary.ys); const yMin = d3.min(attrs.data.primary.ys);
const yMax = d3.max(attrs.data.primary.ys); const yMax = d3.max(attrs.data.primary.ys);
@ -78,36 +198,33 @@ function chart() {
.domain([yMin, yMax]) .domain([yMin, yMax])
.range([calc.chartHeight, 0]); .range([calc.chartHeight, 0]);
const xDateScale3 = d3.scaleLinear()
.domain([new Date(2012, 0, 1), new Date(2020, 0, 31)])
.range([0, calc.chartWidth]);
// Axis generator. // Axis generator.
const xAxis = d3.axisBottom(xScale) const xAxis = d3.axisBottom()
.ticks(3) .scale(xDateScale3)
.tickFormat(d => { .ticks(5)
if (Math.abs(d) < 1) { .tickFormat(format);
return d3.format(".2")(d);
} else if (xMin > 1000 && xMax < 3000) {
// Condition which identifies years; 2019, 2020, 2021.
return d3.format(".0")(d);
} else {
const prefix = d3.formatPrefix(".0", d);
const output = prefix(d);
return output.replace("G", "B");
}
});
// Line generator. // Line generator.
const line = d3.line() const line = d3.line()
.x(function (d, i) { .x(function (d, i) {
console.log("d", d, "i", i);
return xScale(d.x); return xScale(d.x);
}) })
.y(function (d, i) { .y(function (d, i) {
console.log("d", d, "i", i);
return yScale(d.y); return yScale(d.y);
}); });
const area = d3.area() const area = d3.area()
.x(function (d, i) { .x(function (d) {
return xScale(d.x); return xScale(d.x);
}) })
.y1(function (d, i) { .y1(function (d) {
return yScale(d.y); return yScale(d.y);
}) })
.y0(calc.chartHeight); .y0(calc.chartHeight);
@ -120,7 +237,7 @@ function chart() {
.attr('pointer-events', 'none'); .attr('pointer-events', 'none');
// Add container g element. // Add container g element.
const chart = svg this.chart = svg
.patternify({ tag: 'g', selector: 'chart' }) .patternify({ tag: 'g', selector: 'chart' })
.attr( .attr(
'transform', 'transform',
@ -128,16 +245,16 @@ function chart() {
); );
// Add axis. // Add axis.
chart.patternify({ tag: 'g', selector: 'axis' }) this.chart.patternify({ tag: 'g', selector: 'axis' })
.attr('transform', 'translate(' + 0 + ',' + calc.chartHeight + ')') .attr('transform', 'translate(' + 0 + ',' + calc.chartHeight + ')')
.call(xAxis); .call(xAxis);
// Draw area. // Draw area.
chart this.chart
.patternify({ .patternify({
tag: 'path', tag: 'path',
selector: 'area-path', selector: 'area-path',
data: dataPoints data: this.dataPoints
}) })
.attr('d', area) .attr('d', area)
.attr('fill', (d, i) => areaColor(i)) .attr('fill', (d, i) => areaColor(i))
@ -145,11 +262,11 @@ function chart() {
// Draw line. // Draw line.
if (attrs.showDistributionLines) { if (attrs.showDistributionLines) {
chart this.chart
.patternify({ .patternify({
tag: 'path', tag: 'path',
selector: 'line-path', selector: 'line-path',
data: dataPoints data: this.dataPoints
}) })
.attr('d', line) .attr('d', line)
.attr('id', (d, i) => 'line-' + (i + 1)) .attr('id', (d, i) => 'line-' + (i + 1))
@ -160,7 +277,8 @@ function chart() {
} }
if (attrs.showVerticalLine) { if (attrs.showVerticalLine) {
chart.patternify({ tag: 'line', selector: 'v-line' }) this.chart
.patternify({ tag: 'line', selector: 'v-line' })
.attr('x1', xScale(attrs.verticalLine)) .attr('x1', xScale(attrs.verticalLine))
.attr('x2', xScale(attrs.verticalLine)) .attr('x2', xScale(attrs.verticalLine))
.attr('y1', 0) .attr('y1', 0)
@ -170,7 +288,8 @@ function chart() {
.attr('stroke', 'steelblue'); .attr('stroke', 'steelblue');
} }
const hoverLine = chart.patternify({ tag: 'line', selector: 'hover-line' }) this.hoverLine = this.chart
.patternify({ tag: 'line', selector: 'hover-line' })
.attr('x1', 0) .attr('x1', 0)
.attr('x2', 0) .attr('x2', 0)
.attr('y1', 0) .attr('y1', 0)
@ -181,116 +300,55 @@ function chart() {
.attr('stroke', '#22313F'); .attr('stroke', '#22313F');
// Add drawing rectangle. // Add drawing rectangle.
chart.patternify({ tag: 'rect', selector: 'mouse-rect' }) const thi$ = this;
this.chart
.patternify({ tag: 'rect', selector: 'mouse-rect' })
.attr('width', calc.chartWidth) .attr('width', calc.chartWidth)
.attr('height', calc.chartHeight) .attr('height', calc.chartHeight)
.attr('fill', 'transparent') .attr('fill', 'transparent')
.attr('pointer-events', 'all') .attr('pointer-events', 'all')
.on('mouseover', mouseover) .on('mouseover', function () {
.on('mousemove', mouseover) thi$.mouseover(this);
.on('mouseout', mouseout); })
.on('mousemove', function () {
thi$.mouseover(this);
})
.on('mouseout', this.mouseout);
function mouseover() { return this;
const mouse = d3.mouse(this); }
hoverLine.attr('opacity', 1) mouseover(constructor) {
.attr('x1', mouse[0]) const mouse = d3.mouse(constructor);
.attr('x2', mouse[0]); this.hoverLine.attr('opacity', 1)
.attr('x1', mouse[0])
.attr('x2', mouse[0]);
const range = [ const xValue = this.xScale.invert(mouse[0]).toFixed(2);
xScale(dataPoints[dataPoints.length - 1][0].x),
xScale(
dataPoints
[dataPoints.length - 1]
[dataPoints[dataPoints.length - 1].length - 1].x,
),
];
const xValue = xScale.invert(mouse[0]).toFixed(2); const range = [
this.xScale(this.dataPoints[this.dataPoints.length - 1][0].x),
this.xScale(
this.dataPoints
[this.dataPoints.length - 1]
[this.dataPoints[this.dataPoints.length - 1].length - 1].x,
),
];
if (mouse[0] > range[0] && mouse[0] < range[1]) { if (mouse[0] > range[0] && mouse[0] < range[1]) {
attrs.onHover(xValue); this.attrs.onHover(xValue);
} else { } else {
attrs.onHover(0.0); this.attrs.onHover(0.0);
}
}
function mouseout() {
hoverLine.attr('opacity', 0)
}
/**
* @param key
* @returns {[]}
*/
function getDatapoints(key) {
const dt = [];
const data = attrs.data[key];
const len = data.xs.length;
for (let i = 0; i < len; i++) {
dt.push({
x: data.xs[i],
y: data.ys[i]
})
}
return dt;
} }
} }
d3.selection.prototype.patternify = function patternify(params) { mouseout() {
const container = this; this.hoverLine.attr('opacity', 0)
const selector = params.selector; }
const elementTag = params.tag; }
const data = params.data || [selector];
// Pattern in action. function chart() {
const selection = container.selectAll('.' + selector).data(data, (d, i) => { return new Chartigo();
if (typeof d === 'object') {
if (d.id) {
return d.id;
}
}
return i;
});
selection.exit().remove();
const selection2 = selection.enter().append(elementTag).merge(selection);
selection2.attr('class', selector);
return selection2;
};
// @todo: Do not do like that.
// @todo: Dynamic keys functions.
// @todo: Attach variables to main function.
Object.keys(attrs).forEach((key) => {
main[key] = function (_) {
if (!arguments.length) {
return attrs[key];
}
attrs[key] = _;
return main;
};
});
// Set attrs as property.
main.attrs = attrs;
// Exposed update functions.
main.data = function data(value) {
if (!arguments.length) return attrs.data;
attrs.data = value;
return main;
};
// Run visual.
main.render = function render() {
main();
return main;
};
return main;
} }
export default chart; export default chart;