const dLineSeries = fc.seriesSvgLine() .crossValue(d => d.date) .mainValue(d => d.stochastic.d) // use the decorate pattern to access the enter selection and add a class // to this series .decorate(sel => sel.enter() .classed('d-line', true) ); const kLineSeries = fc.seriesSvgLine() .crossValue(d => d.date) .mainValue(d => d.stochastic.k) .decorate(sel => sel.enter() .classed('k-line', true) ); const lineAnnotation = fc.annotationSvgLine(); // merge the annotation and line into a single series that is associated with the chart const mergedSeries = fc.seriesSvgMulti() .series([dLineSeries, kLineSeries, lineAnnotation]) .mapping((data, index, series) => { // the data source for the line series is the bound series, however, the datasource // for the annotation is a hard coded array of values switch(series[index]) { default: return data; case lineAnnotation: return [20, 80]; } }) const chart = fc.chartCartesian( d3.scaleTime(), d3.scaleLinear() ) .yOrient('left') // the oscillator is output on a percentage scale, so requires a domain from 0 - 100 .yDomain([0, 100]) .svgPlotArea(mergedSeries); // use the extent component to determine the x domain const xExtent = fc.extentDate() .accessors([d => d.date]); const parseDate = d3.timeParse("%d-%b-%y"); const stochasticAlgorithm = fc.indicatorStochasticOscillator() .kPeriod(30) .dPeriod(20); d3.csv('data.csv', row => ({ open: Number(row.Open), close: Number(row.Close), high: Number(row.High), low: Number(row.Low), date: parseDate(row.Date) })).then(data => { // the CSV data is in reverse date order data = data.reverse(); // compute the oscillator const stochasticData = stochasticAlgorithm(data); // merge into a single series const mergedData = data.map((d, i) => Object.assign({}, d, { stochastic: stochasticData[i] }) ); // set the domain based on the data chart.xDomain(xExtent(mergedData)); // select and render d3.select('#chart-element') .datum(mergedData) .call(chart); });