export default function() { let multiplier = 2; const slidingWindow = _slidingWindow() .accumulator(values => { const stdDev = values && deviation(values); const average = values && mean(values); return { average: average, upper: convertNaN(average + multiplier * stdDev), lower: convertNaN(average - multiplier * stdDev) }; }); const bollingerBands = data => slidingWindow(data); bollingerBands.multiplier = (...args) => { if (!args.length) { return multiplier; } multiplier = args[0]; return bollingerBands; }; rebind(bollingerBands, slidingWindow, 'period', 'value'); return bollingerBands; }
export default (pathGenerator, seriesName) => { const base = ohlcBase(); const join = dataJoin('g', seriesName); const containerTranslation = (values) => 'translate(' + values.cross + ', ' + values.high + ')'; const propagateTransition = maybeTransition => selection => maybeTransition.selection ? selection.transition(maybeTransition) : selection; const candlestick = (selection) => { if (selection.selection) { join.transition(selection); } const transitionPropagator = propagateTransition(selection); selection.each((data, index, group) => { const filteredData = data.filter(base.defined()); const g = join(select(group[index]), filteredData); g.enter() .attr('transform', (d, i) => containerTranslation(base.values(d, i)) + ' scale(1e-6, 1)') .append('path'); g.each((d, i, g) => { const values = base.values(d, i); const color = values.direction === 'up' ? colors.green : colors.red; const singleCandlestick = transitionPropagator(select(g[i])) .attr('class', seriesName + ' ' + values.direction) .attr('stroke', color) .attr('fill', color) .attr('transform', () => containerTranslation(values) + ' scale(1)'); pathGenerator.x(0) .width(values.width) .open(() => values.open - values.high) .high(0) .low(() => values.low - values.high) .close(() => values.close - values.high); singleCandlestick.select('path') .attr('d', pathGenerator([d])); }); base.decorate()(g, data, index); }); }; rebind(candlestick, join, 'key'); rebindAll(candlestick, base); return candlestick; };
export default () => { const base = xyBase(); const areaData = areaShape(); const join = dataJoin('path', 'area'); const area = (selection) => { if (selection.selection) { join.transition(selection); } areaData.defined(base.defined()); selection.each((data, index, group) => { const projectedData = data.map(base.values); areaData.x((_, i) => projectedData[i].transposedX) .y((_, i) => projectedData[i].transposedY); const valueComponent = base.orient() === 'vertical' ? 'y' : 'x'; areaData[valueComponent + '0']((_, i) => projectedData[i].y0); areaData[valueComponent + '1']((_, i) => projectedData[i].y); const path = join(select(group[index]), [data]); path.enter() .attr('fill', colors.gray); path.attr('d', areaData); base.decorate()(path, data, index); }); }; rebindAll(area, base, exclude('bandwidth', 'align')); rebind(area, join, 'key'); rebind(area, areaData, 'curve'); return area; };
export default () => { const base = multiBase(); const innerJoin = dataJoin('g'); const join = dataJoin('g', 'multi'); const multi = (selection) => { if (selection.selection) { join.transition(selection); innerJoin.transition(selection); } const mapping = base.mapping(); const series = base.series(); const xScale = base.xScale(); const yScale = base.yScale(); selection.each((data, index, group) => { const container = join(select(group[index]), series); // iterate over the containers, 'call'-ing the series for each container.each((dataSeries, seriesIndex, seriesGroup) => { dataSeries.xScale(xScale) .yScale(yScale); const seriesData = mapping(data, seriesIndex, series); const innerContainer = innerJoin(select(seriesGroup[seriesIndex]), [seriesData]); innerContainer.call(dataSeries); }); const unwrappedSelection = container.selection ? container.selection() : container; unwrappedSelection.order(); base.decorate()(container, data, index); }); }; rebindAll(multi, base); rebind(multi, join, 'key'); return multi; };
export default function() { let closeValue = (d, i) => d.close; let highValue = (d, i) => d.high; let lowValue = (d, i) => d.low; const emaComputer = exponentialMovingAverage() .period(13); const elderRay = data => { emaComputer.value(closeValue); return zip(data, emaComputer(data)) .map(d => { const bullPower = convertNaN(highValue(d[0]) - d[1]); const bearPower = convertNaN(lowValue(d[0]) - d[1]); return { bullPower, bearPower }; }); }; elderRay.closeValue = (...args) => { if (!args.length) { return closeValue; } closeValue = args[0]; return elderRay; }; elderRay.highValue = (...args) => { if (!args.length) { return highValue; } highValue = args[0]; return elderRay; }; elderRay.lowValue = (...args) => { if (!args.length) { return lowValue; } lowValue = args[0]; return elderRay; }; rebind(elderRay, emaComputer, 'period'); return elderRay; }
export default () => { const base = boxPlotBase(); const pathGenerator = shapeBoxPlot() .value(0); const boxPlot = (data) => { const filteredData = data.filter(base.defined()); const context = pathGenerator.context(); pathGenerator.orient(base.orient()); filteredData.forEach((d, i) => { context.save(); const values = base.values(d, i); context.translate(values.origin[0], values.origin[1]); context.beginPath(); pathGenerator.median(values.median) .upperQuartile(values.upperQuartile) .lowerQuartile(values.lowerQuartile) .high(values.high) .width(values.width) .low(values.low)([d]); context.fillStyle = colors.gray; context.strokeStyle = colors.black; base.decorate()(context, d, i); context.fill(); context.stroke(); context.closePath(); context.restore(); }); }; rebindAll(boxPlot, base); rebind(boxPlot, pathGenerator, 'cap', 'context'); return boxPlot; };
export default (pathGenerator) => { const base = ohlcBase(); const candlestick = (data) => { const filteredData = data.filter(base.defined()); const context = pathGenerator.context(); filteredData.forEach((d, i) => { context.save(); const values = base.values(d, i); context.translate(values.cross, values.high); context.beginPath(); pathGenerator.x(0) .open(() => values.open - values.high) .width(values.width) .high(0) .low(() => values.low - values.high) .close(() => values.close - values.high)([d]); const color = values.direction === 'up' ? colors.green : colors.red; context.strokeStyle = color; context.fillStyle = color; base.decorate()(context, d, i); context.fill(); context.stroke(); context.closePath(); context.restore(); }); }; rebind(candlestick, pathGenerator, 'context'); rebindAll(candlestick, base); return candlestick; };
export default () => { const symbol = symbolShape(); const base = xyBase(); const point = (data) => { const filteredData = data.filter(base.defined()); const context = symbol.context(); filteredData.forEach((d, i) => { context.save(); const values = base.values(d, i); context.translate(values.origin[0], values.origin[1]); context.beginPath(); symbol(d, i); context.strokeStyle = colors.black; context.fillStyle = colors.gray; base.decorate()(context, d, i); context.fill(); context.stroke(); context.closePath(); context.restore(); }); }; rebindAll(point, base, exclude('baseValue', 'bandwidth', 'align')); rebind(point, symbol, 'size', 'type', 'context'); return point; };
export default () => { const event = dispatch('point'); function mousemove() { const point = mouse(this); event.call('point', this, [{ x: point[0], y: point[1] }]); } function mouseleave() { void event.call('point', this, []); } const instance = (selection) => { selection .on('mouseenter.pointer', mousemove) .on('mousemove.pointer', mousemove) .on('mouseleave.pointer', mouseleave); }; rebind(instance, event, 'on'); return instance; };
const strategyInterceptor = (strategy) => { const interceptor = (layout) => { const start = new Date(); const finalLayout = strategy(layout); const time = new Date() - start; // record some statistics on this strategy if (!interceptor.time) { Object.defineProperty(interceptor, 'time', { enumerable: false, writable: true }); Object.defineProperty(interceptor, 'hidden', { enumerable: false, writable: true }); Object.defineProperty(interceptor, 'overlap', { enumerable: false, writable: true }); } const visibleLabels = finalLayout.filter((d) => !d.hidden); interceptor.time = time; interceptor.hidden = finalLayout.length - visibleLabels.length; interceptor.overlap = sum(visibleLabels.map((label, index) => { return sum(visibleLabels.filter((_, i) => i !== index) .map((d) => layoutIntersect(d, label))); })); return finalLayout; }; rebind(interceptor, strategy, 'bounds'); return interceptor; };
export default () => { let xScale = scaleIdentity(); let yScale = scaleIdentity(); let value = d => d; let label = value; let decorate = () => {}; let orient = 'horizontal'; const lineData = lineShape(); const instance = (data) => { if (orient !== 'horizontal' && orient !== 'vertical') { throw new Error('Invalid orientation'); } const horizontal = orient === 'horizontal'; const context = lineData.context(); // the value scale which the annotation 'value' relates to, the crossScale // is the other. Which is which depends on the orienation! const crossScale = horizontal ? xScale : yScale; const valueScale = horizontal ? yScale : xScale; const crossDomain = crossScale.domain(); const textOffsetX = horizontal ? 9 : 0; const textOffsetY = horizontal ? 0 : 9; const textAlign = horizontal ? 'left' : 'center'; const textBaseline = horizontal ? 'middle' : 'hanging'; data.forEach((d, i) => { context.save(); context.beginPath(); context.strokeStyle = '#bbb'; context.fillStyle = '#000'; context.textAlign = textAlign; context.textBaseline = textBaseline; decorate(context, d, i); // Draw line lineData.context(context)(crossDomain.map(extent => { const point = [crossScale(extent), valueScale(value(d))]; return horizontal ? point : point.reverse(); })); // Draw label const x = horizontal ? crossScale(crossDomain[1]) : valueScale(value(d)); const y = horizontal ? valueScale(value(d)) : crossScale(crossDomain[1]); context.fillText(label(d), x + textOffsetX, y + textOffsetY); context.fill(); context.stroke(); context.closePath(); context.restore(); }); }; instance.xScale = (...args) => { if (!args.length) { return xScale; } xScale = args[0]; return instance; }; instance.yScale = (...args) => { if (!args.length) { return yScale; } yScale = args[0]; return instance; }; instance.value = (...args) => { if (!args.length) { return value; } value = constant(args[0]); return instance; }; instance.label = (...args) => { if (!args.length) { return label; } label = constant(args[0]); return instance; }; instance.decorate = (...args) => { if (!args.length) { return decorate; } decorate = args[0]; return instance; }; instance.orient = (...args) => { if (!args.length) { return orient; } orient = args[0]; return instance; }; rebind(instance, lineData, 'context'); return instance; };
export default () => { let x = (d) => d.x; let y = (d) => d.y; let xScale = scaleIdentity(); let yScale = scaleIdentity(); const point = seriesCanvasPoint(); const horizontalLine = annotationLine(); const verticalLine = annotationLine() .orient('vertical'); // The line annotations and point series used to render the crosshair are positioned using // screen coordinates. This function constructs an identity scale for these components. const xIdentity = scaleIdentity(); const yIdentity = scaleIdentity(); const multi = seriesCanvasMulti() .series([horizontalLine, verticalLine, point]) .xScale(xIdentity) .yScale(yIdentity) .mapping((data) => [data]); const instance = (data) => { data.forEach(d => { // Assign the identity scales an accurate range to allow the line annotations to cover // the full width/height of the chart. xIdentity.range(xScale.range()); yIdentity.range(yScale.range()); point.crossValue(x) .mainValue(y); horizontalLine.value(y); verticalLine.value(x); multi(d); }); }; // Don't use the xValue/yValue convention to indicate that these values are in screen // not domain co-ordinates and are therefore not scaled. instance.x = (...args) => { if (!args.length) { return x; } x = args[0]; return instance; }; instance.y = (...args) => { if (!args.length) { return y; } y = args[0]; return instance; }; instance.xScale = (...args) => { if (!args.length) { return xScale; } xScale = args[0]; return instance; }; instance.yScale = (...args) => { if (!args.length) { return yScale; } yScale = args[0]; return instance; }; const lineIncludes = include('label', 'decorate'); rebindAll(instance, horizontalLine, lineIncludes, prefix('y')); rebindAll(instance, verticalLine, lineIncludes, prefix('x')); rebind(instance, point, 'decorate'); rebind(instance, multi, 'context'); return instance; };