UNPKG

react-financial-charts

Version:
180 lines 8.98 kB
import { ascending, descending, histogram as d3Histogram, max, merge, rollup, sum, zip } from "d3-array"; import { scaleLinear } from "d3-scale"; import * as React from "react"; import GenericChartComponent from "../GenericChartComponent"; import { getAxisCanvas } from "../GenericComponent"; import { accumulatingWindow, colorToRGBA, functor, head, identity, last } from "../utils"; export class VolumeProfileSeries extends React.Component { constructor() { super(...arguments); this.drawOnCanvas = (ctx, moreProps) => { const { xAccessor, width } = moreProps; const { rects, sessionBg } = this.helper(this.props, moreProps, xAccessor, width); this.drawOnCanvasContext(ctx, this.props, rects, sessionBg); }; this.renderSVG = (moreProps) => { const { className, opacity } = this.props; const { showSessionBackground, sessionBackGround, sessionBackGroundOpacity } = this.props; const { xAccessor, width } = moreProps; const { rects, sessionBg } = this.helper(this.props, moreProps, xAccessor, width); const sessionBgSvg = showSessionBackground ? sessionBg.map((d, idx) => React.createElement("rect", Object.assign({ key: idx }, d, { opacity: sessionBackGroundOpacity, fill: sessionBackGround }))) : null; return (React.createElement("g", { className: className }, sessionBgSvg, rects.map((d, i) => React.createElement("g", { key: i }, React.createElement("rect", { x: d.x, y: d.y, width: d.w1, height: d.height, fill: d.fill1, stroke: d.stroke1, fillOpacity: opacity }), React.createElement("rect", { x: d.x + d.w1, y: d.y, width: d.w2, height: d.height, fill: d.fill2, stroke: d.stroke2, fillOpacity: opacity }))))); }; this.drawOnCanvasContext = (ctx, props, rects, sessionBg) => { const { opacity, sessionBackGround, sessionBackGroundOpacity, showSessionBackground } = props; if (showSessionBackground) { ctx.fillStyle = colorToRGBA(sessionBackGround, sessionBackGroundOpacity); sessionBg.forEach((each) => { const { x, y, height, width } = each; ctx.beginPath(); ctx.rect(x, y, width, height); ctx.closePath(); ctx.fill(); }); } rects.forEach((each) => { const { x, y, height, w1, w2, stroke1, stroke2, fill1, fill2 } = each; if (w1 > 0) { ctx.fillStyle = colorToRGBA(fill1, opacity); if (stroke1 !== "none") { ctx.strokeStyle = stroke1; } ctx.beginPath(); ctx.rect(x, y, w1, height); ctx.closePath(); ctx.fill(); if (stroke1 !== "none") { ctx.stroke(); } } if (w2 > 0) { ctx.fillStyle = colorToRGBA(fill2, opacity); if (stroke2 !== "none") { ctx.strokeStyle = stroke2; } ctx.beginPath(); ctx.rect(x + w1, y, w2, height); ctx.closePath(); ctx.fill(); if (stroke2 !== "none") { ctx.stroke(); } } }); }; this.helper = (props, moreProps, xAccessor, width) => { const { xScale: realXScale, chartConfig: { yScale }, plotData } = moreProps; const { sessionStart, bySession, partialStartOK, partialEndOK } = props; const { bins, maxProfileWidthPercent, source, volume, absoluteChange, orient, fill, stroke } = props; const sessionBuilder = accumulatingWindow() .discardTillStart(!partialStartOK) // @ts-ignore .discardTillEnd(!partialEndOK) .accumulateTill((d, i) => { return sessionStart(Object.assign({ d, i }, moreProps)); }) .accumulator(identity); const dx = plotData.length > 1 ? realXScale(xAccessor(plotData[1])) - realXScale(xAccessor(head(plotData))) : 0; const sessions = bySession ? sessionBuilder(plotData) : [plotData]; const allRects = sessions.map((session) => { const begin = bySession ? realXScale(xAccessor(head(session))) : 0; const finish = bySession ? realXScale(xAccessor(last(session))) : width; const sessionWidth = finish - begin + dx; const histogram2 = d3Histogram() .value(source) .thresholds(bins); const rolledup = (data) => { const sortFunction = orient === "right" ? descending : ascending; const sortedData = data.sort(sortFunction); return rollup(sortedData, (leaves) => sum(leaves, (d) => d.volume), (d) => d.direction); }; const values = histogram2(session); const volumeInBins = values .map((arr) => arr.map((d) => { return absoluteChange(d) > 0 ? { direction: "up", volume: volume(d) } : { direction: "down", volume: volume(d) }; })) .map((arr) => Array.from(rolledup(arr))); const volumeValues = volumeInBins .map((each) => sum(each.map((d) => d[1]))); const base = (xScaleD) => head(xScaleD.range()); const [start, end] = orient === "right" ? [begin, begin + sessionWidth * maxProfileWidthPercent / 100] : [finish, finish - sessionWidth * (100 - maxProfileWidthPercent) / 100]; const xScale = scaleLinear() .domain([0, max(volumeValues)]) .range([start, end]); const totalVolumes = volumeInBins.map((volumes) => { const totalVolume = sum(volumes, (d) => d.value); const totalVolumeX = xScale(totalVolume); const widthLocal = base(xScale) - totalVolumeX; const x = widthLocal < 0 ? totalVolumeX + widthLocal : totalVolumeX; const ws = volumes.map((d) => { return { type: d[0], width: d[1] * Math.abs(widthLocal) / totalVolume, }; }); return { x, ws, totalVolumeX }; }); // @ts-ignore const rects = zip(values, totalVolumes) .map(([d, { x, ws }]) => { const w1 = ws[0] || { type: "up", width: 0 }; const w2 = ws[1] || { type: "down", width: 0 }; return { y: yScale(d.x1), height: yScale(d.x1) - yScale(d.x0), x, width, w1: w1.width, w2: w2.width, stroke1: functor(stroke)(w1), stroke2: functor(stroke)(w2), fill1: functor(fill)(w1), fill2: functor(fill)(w2), }; }); const sessionBg = { x: begin, y: last(rects).y, height: head(rects).y - last(rects).y + head(rects).height, width: sessionWidth, }; return { rects, sessionBg }; }); return { rects: merge(allRects.map((d) => d.rects)), sessionBg: allRects.map((d) => d.sessionBg), }; }; } render() { return (React.createElement(GenericChartComponent, { svgDraw: this.renderSVG, canvasDraw: this.drawOnCanvas, canvasToDraw: getAxisCanvas, drawOn: ["pan"] })); } } VolumeProfileSeries.defaultProps = { className: "line ", bins: 20, opacity: 0.5, maxProfileWidthPercent: 50, fill: ({ type }) => type === "up" ? "#6BA583" : "#FF0000", stroke: "#FFFFFF", showSessionBackground: false, sessionBackGround: "#4682B4", sessionBackGroundOpacity: 0.3, source: (d) => d.close, volume: (d) => d.volume, absoluteChange: (d) => d.absoluteChange, bySession: false, sessionStart: ({ d, i, plotData }) => i > 0 && plotData[i - 1].date.getMonth() !== d.date.getMonth(), orient: "left", partialStartOK: true, partialEndOK: true, }; //# sourceMappingURL=VolumeProfileSeries.js.map