Colors borrowed from bitcoin wisdom, but chart engine is custom and using SVG instead of Canvas.
Created
November 18, 2018 04:13
-
-
Save markcheno/f776b0bff045c00d90d6a8dcde86d1d1 to your computer and use it in GitHub Desktop.
D3 Candlestick Chart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| div(id="main") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (function () { | |
| var | |
| utils = window.utilities, | |
| identity = function(v){return v;}; | |
| var | |
| PROP_EL = '_el', | |
| PROP_SVG = '_svg', | |
| PROP_DATA = '_data', | |
| PROP_WIDTH = '_width', | |
| PROP_HEIGHT = '_height', | |
| PROP_BAR_SPACING = '_barSpacing', | |
| PROP_DPROP_LOW = '_propertyLow', | |
| PROP_DPROP_HIGH = '_propertyHigh', | |
| PROP_DPROP_VOL = '_propertyVolume', | |
| PROP_DPROP_DATE = '_propertyDate', | |
| PROP_DPROP_CLOSE = '_propertyClose', | |
| PROP_DPROP_OPEN = '_propertyOpen', | |
| PROP_MAR_CANDLE = '_marginCandle', | |
| PROP_MAR_VOLUME = '_marginVolume', | |
| PROP_COL_CANDBG = '_colorCandleBackground', | |
| PROP_COL_BODYDN = '_colorBodyDown', | |
| PROP_COL_BODYUP = '_colorBodyUp', | |
| PROP_COL_STEMDN = '_colorStemDown', | |
| PROP_COL_STEMUP = '_colorStemUp', | |
| PROP_COL_VOLBG = '_colorVolBackground', | |
| PROP_COL_VOLDN = '_colorVolSell', | |
| PROP_COL_VOLUP = '_colorVolBuy', | |
| PROP_COL_VOLDNBORD = '_colorVolSellBorder', | |
| PROP_COL_VOLUPBORD = '_colorVolBuyBorder', | |
| PROP_COL_LABTIMEBG = '_colorLabelTimeBackground', | |
| PROP_COL_LABTIMEFG = '_colorLabelTimeForeground', | |
| PROP_COL_LABCANBG = '_colorLabelCandleBackground', | |
| PROP_COL_LABCANFG = '_colorLabelCandleForeground', | |
| PROP_COL_LABVOLBG = '_colorLabelVolumeBackground', | |
| PROP_COL_LABVOLFG = '_colorLabelVolumeForeground', | |
| PROP_LAB_FAM = '_labelFontFamily', | |
| PROP_LAB_SIZE = '_labelFontSize'; | |
| function D3CandleStickChart (el, props) { | |
| this[PROP_WIDTH] = 320; | |
| this[PROP_HEIGHT] = 240; | |
| this[PROP_DATA] = []; | |
| if(window.d3 === undefined) { | |
| throw new Error('Unable to locate the d3 library'); | |
| } | |
| props = utils.isPlainObject(props) ? props : {}; | |
| // load any data in the props | |
| this.loadData(props.data); | |
| this.width = props.width; | |
| this.height = props.height; | |
| this.parent = el || document.body; | |
| this[PROP_DPROP_LOW] = props.propertyLow || 'low'; | |
| this[PROP_DPROP_HIGH] = props.propertyHigh || 'high'; | |
| this[PROP_DPROP_VOL] = props.propertyVolume || 'volume'; | |
| this[PROP_DPROP_DATE] = props.propertyDate || 'date'; | |
| this[PROP_DPROP_CLOSE] = props.propertyClose || 'close'; | |
| this[PROP_DPROP_OPEN] = props.propertyOpen || 'open'; | |
| this[PROP_BAR_SPACING] = props.barSpacing || 0.35; | |
| this[PROP_MAR_CANDLE] = props.marginCandle || 15; | |
| this[PROP_MAR_VOLUME] = props.marginVolume || 5; | |
| this[PROP_COL_CANDBG] = props.colorCandleBackground || '#111'; | |
| this[PROP_COL_BODYDN] = props.colorCandleBodyDown || '#FF2121'; | |
| this[PROP_COL_BODYUP] = props.colorCandleBodyUp || this[PROP_COL_CANDBG]; | |
| this[PROP_COL_STEMDN] = props.colorCandleStemDown || this[PROP_COL_BODYDN]; | |
| this[PROP_COL_STEMUP] = props.colorCandleStemUp || '#1BD31B'; | |
| this[PROP_COL_VOLBG] = props.colorVolumeSell || '#222'; | |
| this[PROP_COL_VOLDN] = props.colorVolumeBuy || this[PROP_COL_BODYDN]; | |
| this[PROP_COL_VOLUP] = props.colorVolumeSellBorder || this[PROP_COL_VOLBG]; | |
| this[PROP_COL_VOLDNBORD] = props.colorVolumeBuyBorder || this[PROP_COL_BODYDN]; | |
| this[PROP_COL_VOLUPBORD] = props.colorVolumeBackground || this[PROP_COL_STEMUP]; | |
| this[PROP_COL_LABTIMEBG] = props.colorLabelTimeBackground || '#333'; | |
| this[PROP_COL_LABTIMEFG] = props.colorLabelTimeForeground || '#aaa'; | |
| this[PROP_COL_LABCANBG] = props.colorLabelCandleBackground || '#333'; | |
| this[PROP_COL_LABCANFG] = props.colorLabelCandleForeground || '#aaa'; | |
| this[PROP_COL_LABVOLBG] = props.colorLabelVolumeBackground || '#444'; | |
| this[PROP_COL_LABVOLFG] = props.colorLabelVolumeForeground || '#bbb'; | |
| this[PROP_LAB_FAM] = props.labelFontFamily || 'Ubuntu'; | |
| this[PROP_LAB_SIZE] = props.labelFontSize || 10; | |
| } | |
| function redrawIdent () { | |
| return function (val) { | |
| this.redraw(this.svg, this.data); | |
| return val; | |
| }; | |
| } | |
| function redrawGetSetter (prop, allowNull) { | |
| return { | |
| get: utils.getter(prop), | |
| set: utils.setter(prop, allowNull, redrawIdent()), | |
| }; | |
| } | |
| Object.defineProperties(D3CandleStickChart.prototype, { | |
| parent: { // auto-install if this gets changed. | |
| get: utils.getter(PROP_EL), | |
| set: utils.setter(PROP_EL, false, function (parent) { | |
| if (parent) { | |
| if (this.parent) { // un-install any previous charts in parent | |
| this.uninstall(this.parent); | |
| } | |
| return this.install(parent); | |
| } | |
| return this.parent; | |
| }) | |
| }, | |
| svg: { | |
| get: utils.getter(PROP_SVG) | |
| }, | |
| width: { | |
| get: utils.getter(PROP_WIDTH), | |
| set: utils.setterInt(PROP_WIDTH, 0) | |
| }, | |
| height: { | |
| get: utils.getter(PROP_HEIGHT), | |
| set: utils.setterInt(PROP_HEIGHT, 0) | |
| }, | |
| aspectRatio: { | |
| get: function () { | |
| return this.height / this.width; | |
| } | |
| }, | |
| data: { | |
| get: utils.getter(PROP_DATA) | |
| }, | |
| propertyLow: redrawGetSetter(PROP_DPROP_LOW), | |
| propertyHigh: redrawGetSetter(PROP_DPROP_HIGH), | |
| propertyVolume: redrawGetSetter(PROP_DPROP_VOL), | |
| propertyDate: redrawGetSetter(PROP_DPROP_DATE), | |
| propertyClose: redrawGetSetter(PROP_DPROP_CLOSE), | |
| propertyOpen: redrawGetSetter(PROP_DPROP_OPEN), | |
| barSpacing: redrawGetSetter(PROP_BAR_SPACING), | |
| marginCandle: redrawGetSetter(PROP_MAR_CANDLE), | |
| marginVolume: redrawGetSetter(PROP_MAR_VOLUME), | |
| colorCandleBackground: redrawGetSetter(PROP_COL_CANDBG), | |
| colorCandleBodyDown: redrawGetSetter(PROP_COL_BODYDN), | |
| colorCandleBodyUp: redrawGetSetter(PROP_COL_BODYUP), | |
| colorCandleStemDown: redrawGetSetter(PROP_COL_STEMDN), | |
| colorCandleStemUp: redrawGetSetter(PROP_COL_STEMUP), | |
| colorVolumeSell: redrawGetSetter(PROP_COL_VOLDN), | |
| colorVolumeBuy: redrawGetSetter(PROP_COL_VOLUP), | |
| colorVolumeSellBorder: redrawGetSetter(PROP_COL_VOLDNBORD), | |
| colorVolumeBuyBorder: redrawGetSetter(PROP_COL_VOLUPBORD), | |
| colorVolumeBackground: redrawGetSetter(PROP_COL_VOLBG), | |
| colorLabelTimeBackground: redrawGetSetter(PROP_COL_LABTIMEBG), | |
| colorLabelTimeForeground: redrawGetSetter(PROP_COL_LABTIMEFG), | |
| colorLabelCandleBackground: redrawGetSetter(PROP_COL_LABCANBG), | |
| colorLabelCandleForeground: redrawGetSetter(PROP_COL_LABCANFG), | |
| colorLabelVolumeBackground: redrawGetSetter(PROP_COL_LABVOLBG), | |
| colorLabelVolumeForeground: redrawGetSetter(PROP_COL_LABVOLFG), | |
| labelFontFamily: redrawGetSetter(PROP_LAB_FAM), | |
| labelFontSize: redrawGetSetter(PROP_LAB_SIZE) | |
| }); | |
| D3CandleStickChart.low = function (prev, next) { | |
| if(prev === null) return next; | |
| return (next===null) ? prev : Math.min(prev, next); | |
| }; | |
| D3CandleStickChart.high = function (prev, next) { | |
| if(prev === null) return next; | |
| return (next===null) ? prev : Math.max(prev, next); | |
| }; | |
| D3CandleStickChart.dateMS = function (v) { | |
| if(v instanceof Date) return v.getTime(); | |
| if(typeof v === 'string') { | |
| var ms = Date.parse(v); | |
| if(isNaN(ms)) return null; | |
| return ms; | |
| } | |
| if(typeof v === 'number'&&!isNaN(v)) return v; | |
| return null; | |
| }; | |
| D3CandleStickChart.lowDate = function (prev, next) { | |
| return D3CandleStickChart.low(prev, D3CandleStickChart.dateMS(next)); | |
| }; | |
| D3CandleStickChart.highDate = function (prev, next) { | |
| return D3CandleStickChart.high(prev, D3CandleStickChart.dateMS(next)); | |
| }; | |
| D3CandleStickChart.dateSeriesMinutes = function (ms) { | |
| return moment(ms).format('mm:ss'); | |
| }; | |
| D3CandleStickChart.dateSeriesHours = function (ms) { | |
| return moment(ms).format('HH:mm'); | |
| }; | |
| D3CandleStickChart.dateSeriesDays = function (ms) { | |
| return moment(ms).format('MMM, DD'); | |
| }; | |
| D3CandleStickChart.dateSeriesMonths = function (ms) { | |
| return moment(ms).format('MMM, YYYY'); | |
| }; | |
| D3CandleStickChart.dateSeriesYears = function (ms) { | |
| return moment(ms).format('MMM, YYYY'); | |
| }; | |
| D3CandleStickChart.dateSeriesFunctionFromDiff = function (start, end) { | |
| var diff = Math.abs(D3CandleStickChart.dateMS(start) - D3CandleStickChart.dateMS(end)); | |
| return (diff >= 3.1104e+10 | |
| ? D3CandleStickChart.dateSeriesYears : (diff >= 2.592e+9 | |
| ? D3CandleStickChart.dateSeriesMonths : (diff >= 1.728e+8 | |
| ? D3CandleStickChart.dateSeriesDays : (diff >= 3.6e+6 | |
| ? D3CandleStickChart.dateSeriesHours : D3CandleStickChart.dateSeriesMinutes | |
| )))); | |
| }; | |
| D3CandleStickChart.getScales = function (data, spec) { | |
| spec = spec || {}; | |
| var | |
| propLow = spec.propertyLow ||'low', | |
| propHigh = spec.propertyHigh ||'high', | |
| propVol = spec.propertyVolume ||'volume', | |
| propDate = spec.propertyDate ||'date'; | |
| return data.reduce(function (p, c) { // find all domains using a single data iteration (optimal) | |
| p.lowestLow = D3CandleStickChart.low(p.lowestLow, c[propLow]); | |
| p.highestHigh = D3CandleStickChart.high(p.highestHigh, c[propHigh]); | |
| p.volumeLow = D3CandleStickChart.low(p.volumeLow, c[propVol]); | |
| p.volumeHigh = D3CandleStickChart.high(p.volumeHigh, c[propVol]); | |
| p.dateLow = D3CandleStickChart.lowDate(p.dateLow, c[propDate]); | |
| p.dateHigh = D3CandleStickChart.highDate(p.dateHigh, c[propDate]); | |
| return p; | |
| }, { // seed the unprocessed domains | |
| lowestLow: null, | |
| highestHigh: null, | |
| volumeLow: null, | |
| volumeHigh: null, | |
| dateLow: null, | |
| dateHigh: null | |
| }); | |
| }; | |
| D3CandleStickChart.prototype.resize = function (width, height, svg) { | |
| svg = svg || this.svg; | |
| width = width || this.width; | |
| height = height || this.height; | |
| svg.attr('width', width); | |
| svg.attr('height', height); | |
| if(width !== this.width) { | |
| this.width = width; | |
| } | |
| if(height !== this.height) { | |
| this.height = height; | |
| } | |
| return this; | |
| }; | |
| D3CandleStickChart.prototype.clearData = function () { | |
| this.data.splice(0, this.data.length); | |
| this.reset(); | |
| return true; | |
| }; | |
| D3CandleStickChart.prototype.loadData = function (data) { | |
| if(!utils.isArray(data)) return false; | |
| Array.prototype.push.apply(this.data, data); | |
| if(this.parent) this.redraw(); | |
| return true; | |
| }; | |
| D3CandleStickChart.prototype.reset = function (svg) { | |
| svg = svg || this.svg; | |
| this.resetCandles(svg); | |
| this.resetVolume(svg); | |
| this.resetLabels(svg); | |
| }; | |
| D3CandleStickChart.prototype.getScales = function (data) { | |
| return D3CandleStickChart.getScales(data || this.data, { | |
| propertyLow: this.propertyLow, | |
| propertyHigh: this.propertyHigh, | |
| propertyVolume: this.propertyVolume, | |
| propertyDate: this.propertyDate | |
| }); | |
| }; | |
| D3CandleStickChart.prototype.coordCandles = function () { | |
| var | |
| volcoord = this.coordVolume(); | |
| return { | |
| x: volcoord.x, | |
| y: 0, | |
| h: volcoord.y, | |
| w: volcoord.w | |
| }; | |
| }; | |
| D3CandleStickChart.prototype.resetCandles = function (svg) { | |
| svg.selectAll('g.candles').remove(); | |
| return this; | |
| }; | |
| D3CandleStickChart.prototype.redrawCandles = function (svg, scales) { | |
| var | |
| propLow = this.propertyLow, | |
| propHigh = this.propertyHigh, | |
| propVol = this.propertyVolume, | |
| propDate = this.propertyDate, | |
| propClose = this.propertyClose, | |
| propOpen = this.propertyOpen, | |
| colorBG = this.colorCandleBackground, | |
| colorBodyDown = this.colorCandleBodyDown, | |
| colorBodyUp = this.colorCandleBodyUp, | |
| colorStemDown = this.colorCandleStemDown, | |
| colorStemUp = this.colorCandleStemUp, | |
| margin = this.marginCandle || 0, | |
| coord = this.coordCandles(), | |
| approxW = (coord.w / this.data.length), | |
| barSpace = approxW * this.barSpacing, | |
| barWidth = approxW - barSpace, | |
| stemWidth = barWidth * 0.15, | |
| group = svg.append('g') | |
| .attr('class', 'candles') | |
| .attr('transform', 'translate('+[coord.x, coord.y].join(' ') +')'), | |
| calcDateX = d3.scale.linear() | |
| .domain([scales.dateLow, scales.dateHigh]) | |
| .range([0, coord.w - barWidth]), | |
| calcRY = d3.scale.linear() | |
| .domain([scales.lowestLow, scales.highestHigh]) | |
| .range([coord.h - margin, margin]); | |
| if(stemWidth < 1) { | |
| stemWidth = 1; | |
| } | |
| if(barWidth < 1) { | |
| barWidth = 1; | |
| } | |
| group.append('rect') | |
| .attr('class', 'background') | |
| .attr('width', coord.w) | |
| .attr('height', coord.h) | |
| .attr('fill', colorBG); | |
| var | |
| dOpenCloseMin = function (d) { return Math.min(d[propClose], d[propOpen]); }, | |
| dOpenCloseMax = function (d) { return Math.max(d[propClose], d[propOpen]); }, | |
| dIsDown = function (d) { return d[propClose] < d[propOpen]; }, | |
| dStemColor = function (d) { return dIsDown(d) ? colorStemDown : colorStemUp; }, | |
| dBodyColor = function (d) { return dIsDown(d) ? colorBodyDown : colorBodyUp; }; | |
| if(stemWidth > 0) { | |
| group.selectAll('rect.candle-stem') | |
| .data(this.data) | |
| .enter().append('rect') | |
| .attr('class', 'candle-stem') | |
| .attr('width', stemWidth) | |
| .attr('x', function (d) { return calcDateX(d[propDate]) + ((barWidth/2) - (stemWidth/2)); }) | |
| .attr('y', function (d) { | |
| return calcRY(d[propHigh]); | |
| }) | |
| .attr('height', function (d) { | |
| return calcRY(d[propLow]) - calcRY(d[propHigh]); | |
| }) | |
| .attr('fill', dStemColor); | |
| } | |
| group.selectAll('rect.candle-body') | |
| .data(this.data) | |
| .enter().append('rect') | |
| .attr('class', 'candle-body') | |
| .attr('width', barWidth) | |
| .attr('vmin', dOpenCloseMin) | |
| .attr('vmax', dOpenCloseMax) | |
| .attr('x', function (d) { return calcDateX(d[propDate]); }) | |
| .attr('y', function (d) { return calcRY(this.getAttribute('vmax')); }) | |
| .attr('height', function (d) { | |
| return calcRY(this.getAttribute('vmin'))-calcRY(this.getAttribute('vmax')); | |
| }) | |
| .attr('stroke-width', 0.5) | |
| .attr('stroke-linecap', 'butt') | |
| .attr('stroke-rendering', 'crispEdges') | |
| .attr('stroke', dStemColor) | |
| .attr('fill', dBodyColor); | |
| return svg; | |
| }; | |
| D3CandleStickChart.prototype.coordVolume = function () { | |
| var | |
| volheight = 100, | |
| coordlabels = this.coordLabels(), | |
| coordSX = coordlabels.seriesX; | |
| if(volheight / this.height > 0.25) { | |
| volheight = 50; | |
| } | |
| return { | |
| x: coordSX.x, | |
| y: coordSX.y - volheight, | |
| w: coordSX.w, | |
| h: volheight | |
| }; | |
| }; | |
| D3CandleStickChart.prototype.resetVolume = function (svg) { | |
| svg.selectAll('g.volume').remove(); | |
| return this; | |
| }; | |
| D3CandleStickChart.prototype.redrawVolume = function (svg, scales) { | |
| var | |
| coord = this.coordVolume(), | |
| propVol = this.propertyVolume, | |
| propDate = this.propertyDate, | |
| propClose = this.propertyClose, | |
| propOpen = this.propertyOpen, | |
| colorVolSell = this.colorVolumeSell, | |
| colorVolBuy = this.colorVolumeBuy, | |
| colorVolSellBorder = this.colorVolumeSellBorder, | |
| colorVolBuyBorder = this.colorVolumeBuyBorder, | |
| colorBG = this.colorVolumeBackground, | |
| approxW = (coord.w / this.data.length), | |
| barSpace = approxW * this.barSpacing, | |
| barWidth = approxW - barSpace, | |
| margin = this.marginVolume || 0; | |
| if(barWidth < 1) { | |
| barWidth = 1; | |
| } | |
| var | |
| group = svg.append('g') | |
| .attr('class', 'volume') | |
| .attr('transform', 'translate('+[coord.x, coord.y].join(' ') +')'), | |
| calcDateX = d3.scale.linear() | |
| .domain([scales.dateLow, scales.dateHigh]) | |
| .range([0, coord.w - barWidth]), | |
| calcVolHeight = d3.scale.linear() | |
| .domain([scales.volumeLow, scales.volumeHigh]) | |
| .range([2, coord.h - margin]), | |
| dIsDown = function (d) { return d[propClose] < d[propOpen]; }, | |
| dColorBars = function (d) { return dIsDown(d) ? colorVolSell : colorVolBuy; }, | |
| dColorBarsBord = function (d) { return dIsDown(d) ? colorVolSellBorder : colorVolBuyBorder; }; | |
| group.append('rect') | |
| .attr('class', 'background') | |
| .attr('width', coord.w) | |
| .attr('height', coord.h) | |
| .attr('fill', colorBG); | |
| group.selectAll('rect.volume-bar') | |
| .data(this.data) | |
| .enter().append('rect') | |
| .attr('class', 'volume-bar') | |
| .attr('fill', dColorBars) | |
| .attr('stroke', dColorBarsBord) | |
| .attr('width', barWidth) | |
| .attr('stroke-width', 0.5) | |
| .attr('stroke-linecap', 'butt') | |
| .attr('stroke-rendering', 'crispEdges') | |
| .attr('height', function (d) { | |
| return calcVolHeight(d[propVol]); | |
| }) | |
| .attr('x', function (d) { | |
| return calcDateX(d[propDate]); | |
| }) | |
| .attr('y', function (d) { | |
| return (coord.h - this.getAttribute('height')); | |
| }); | |
| return svg; | |
| }; | |
| D3CandleStickChart.prototype.coordLabels = function () { | |
| var | |
| xheight = 20, | |
| ywidth = 75; | |
| return { | |
| seriesX: { | |
| x: 0, | |
| y: this.height - xheight, | |
| w: this.width - ywidth, | |
| h: xheight | |
| }, | |
| seriesY: { | |
| x: this.width - ywidth, | |
| y: 0, | |
| w: ywidth, | |
| h: this.height | |
| } | |
| }; | |
| }; | |
| D3CandleStickChart.prototype.resetLabels = function (svg) { | |
| svg.selectAll('g.labels').remove(); | |
| return this; | |
| }; | |
| D3CandleStickChart.prototype.redrawLabels = function (svg, scales) { | |
| var | |
| coord = this.coordLabels(), | |
| coordvol = this.coordVolume(), | |
| coordcand = this.coordCandles(), | |
| fontFamily = this.labelFontFamily, | |
| fontSize = this.labelFontSize, | |
| colorTimeBG = this.colorLabelTimeBackground, | |
| colorTimeFG = this.colorLabelTimeForeground, | |
| colorCandleBG = this.colorLabelCandleBackground, | |
| colorCandleFG = this.colorLabelCandleForeground, | |
| colorVolumeBG = this.colorLabelVolumeBackground, | |
| colorVolumeFG = this.colorLabelVolumeForeground, | |
| marginCandle = this.marginCandle || 0, | |
| marginVolume = this.marginVolume || 0, | |
| cSX = coord.seriesX, | |
| cSY = coord.seriesY, | |
| groupY = svg.append('g') | |
| .attr('class', 'labels label-y') | |
| .attr('transform', 'translate('+[cSY.x, cSY.y].join(' ') +')'), | |
| groupX = svg.append('g') | |
| .attr('class', 'labels label-x') | |
| .attr('transform', 'translate('+[cSX.x, cSX.y].join(' ') +')'), | |
| // time series data & functions: | |
| timeticks = 10, | |
| dateX = d3.scale.linear() | |
| .domain([scales.dateLow, scales.dateHigh]) | |
| .range([cSX.x, cSX.x + cSX.w]), | |
| // candle data & functions: | |
| candticks = 6, | |
| candY = d3.scale.linear() | |
| .domain([scales.lowestLow, scales.highestHigh]) | |
| .range([coordcand.y + coordcand.h - marginCandle, coordcand.y + marginCandle]), | |
| // volume data & functions: | |
| volticks = 5, | |
| volY = d3.scale.linear() | |
| .domain([scales.volumeLow, scales.volumeHigh]) | |
| .range([coordvol.y + coordvol.h, coordvol.y + fontSize + marginVolume]); | |
| groupX.append('rect') // placeholder for time-series labels | |
| .attr('width', cSX.w) | |
| .attr('height', cSX.h) | |
| .attr('fill', colorTimeBG); | |
| groupY.append('rect') // placeholder for X labels (end-cap) | |
| .attr('y', cSX.y) | |
| .attr('width', cSY.w) | |
| .attr('height', cSX.h) | |
| .attr('fill', colorTimeBG); | |
| groupX.selectAll('text.series-x') | |
| .data(dateX.ticks(timeticks)) | |
| .enter().append('svg:text') | |
| .attr('class', 'series-x') | |
| .attr('x', dateX) | |
| .attr('y', 0) | |
| .attr('dy', (cSX.h/2)+(fontSize/2)) | |
| .attr('font-family', fontFamily) | |
| .attr('font-size', fontSize) | |
| .attr('text-anchor', 'middle') | |
| .attr('fill', colorTimeFG) | |
| .text(D3CandleStickChart.dateSeriesFunctionFromDiff(scales.dateLow, scales.dateHigh)); | |
| // candle labels | |
| groupY.append('rect') // background | |
| .attr('y', coordcand.y) | |
| .attr('width', cSY.w) | |
| .attr('height', coordcand.h) | |
| .attr('fill', colorCandleBG); | |
| groupY.selectAll('text.candle-label') // ticks | |
| .data(candY.ticks(candticks)) | |
| .enter().append('text') | |
| .attr('class', 'candle-label') | |
| .attr('x', 0) | |
| .attr('y', candY) | |
| .attr('dx', cSY.w/2) | |
| // .attr('dy', fontSize/2) | |
| .attr('font-family', fontFamily) | |
| .attr('font-size', fontSize) | |
| .attr('text-anchor', 'middle') | |
| .attr('fill', colorCandleFG) | |
| .text(function (d) { | |
| return utils.isNumber(d) ? ( d < 1 ? d.toFixed(4) : d) : d; | |
| }); | |
| // volume labels: | |
| groupY.append('rect') // background | |
| .attr('y', coordvol.y) | |
| .attr('width', cSY.w) | |
| .attr('height', coordvol.h) | |
| .attr('fill', colorVolumeBG); | |
| groupY.selectAll('text.vol-label') // ticks | |
| .data(volY.ticks(volticks)) | |
| .enter().append('text') | |
| .attr('class', 'vol-label') | |
| .attr('x', 0) | |
| .attr('y', volY) | |
| .attr('dx', cSY.w/2) | |
| .attr('dy', 0) | |
| .attr('fill', colorVolumeFG) | |
| .attr('font-family', fontFamily) | |
| .attr('font-size', fontSize) | |
| .attr('text-anchor', 'middle') | |
| .text(String); | |
| return svg; | |
| }; | |
| D3CandleStickChart.prototype.redraw = function (svg, data) { | |
| svg = svg || this.svg; | |
| data = data || this.data; | |
| // clear existing charted data | |
| this.reset(svg); | |
| if(!data.length) { // bail out if no data | |
| return svg; | |
| } | |
| // resize SVG to outer limits | |
| svg.attr('width', this.width); | |
| svg.attr('height', this.height); | |
| svg.attr('viewBox', '0 0 '+ this.width + ' ' + this.height); | |
| var | |
| /***** scales: | |
| * lowestLow | |
| * highestHigh | |
| * volumeLow | |
| * volumeHigh | |
| * dateLow | |
| * dateHigh | |
| *****/ | |
| scales = this.getScales(data); | |
| this.redrawCandles(svg, scales); | |
| this.redrawVolume(svg, scales); | |
| this.redrawLabels(svg, scales); | |
| }; | |
| D3CandleStickChart.prototype.initView = function (svg) { | |
| this.resize(this.width, this.height, svg); | |
| this.redraw(svg, this.data); | |
| return svg; | |
| }; | |
| D3CandleStickChart.prototype.install = function (parent) { | |
| parent = parent || this.parent; | |
| if(!parent) { | |
| return false; | |
| } | |
| // set the parent's svg context | |
| this[PROP_SVG] = this.initView(d3.select(parent).append('svg:svg')); | |
| return parent; | |
| }; | |
| D3CandleStickChart.prototype.uninstall = function (parent) { | |
| d3.select(parent||this.parent) | |
| .select('svg') | |
| .remove(); | |
| return parent; | |
| }; | |
| this.D3CandleStickChart = D3CandleStickChart; | |
| }).call(this); | |
| // | |
| // Test code | |
| // | |
| (function (el) { | |
| if(!el) throw new Error('Unable to find main canvas.'); | |
| var | |
| chart = new window.D3CandleStickChart(el, {}), | |
| // data loading stuff | |
| msPerSec = 1000, | |
| pair = 'BTC_BTS', | |
| period = 14400, | |
| num = 100, | |
| epocEnd = Math.round(Date.now()/msPerSec), | |
| epocStart = Math.round(epocEnd - (period * num)); | |
| function reloadData() { | |
| $.ajax({ // fetch some chart data | |
| url: 'https://poloniex.com/public?command=returnChartData¤cyPair='+pair+'&start='+epocStart+'&end='+epocEnd+'&period='+period, | |
| json: true, | |
| success: function (data) { | |
| chart.clearData(); | |
| chart.loadData(data.map(function (record) { | |
| record.date *= msPerSec; | |
| return record; | |
| })); | |
| } | |
| }); | |
| } | |
| reloadData(); | |
| setInterval(reloadData, Math.round((period * msPerSec)/2)); | |
| (window.onresize = function () { | |
| chart.resize(window.innerWidth, window.innerHeight); | |
| chart.redraw(); | |
| })(); | |
| })(document.getElementById('main')) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js"></script> | |
| <script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.3/moment.min.js"></script> | |
| <script src="https://cdn.rawgit.com/kryo2k/utils.js/master/index.js"></script> | |
| <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| @import url(https://fonts.googleapis.com/css?family=Ubuntu); | |
| @import url(https://fonts.googleapis.com/css?family=Roboto); | |
| html, | |
| body { | |
| overflow: hidden; | |
| margin: 0; | |
| padding: 0; | |
| height: 100% | |
| } | |
| body { | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| background: #111; | |
| } | |
| svg { | |
| // outline: 1px dashed white; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment