Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to show chart grid? #230

Open
icastillejogomez opened this issue Oct 1, 2018 · 16 comments
Open

How to show chart grid? #230

icastillejogomez opened this issue Oct 1, 2018 · 16 comments

Comments

@icastillejogomez
Copy link

I'm developing a new trade app and we need to show the chart grid in our chart. Is it possible?

@briancappello
Copy link
Contributor

briancappello commented Oct 1, 2018

It's been a while since I've worked with this package, so I'm just copying the following from a working app, but hopefully it can at least help point you in the right direction:

// https://github.com/d3/d3-format#api-reference
const DEC = ',.2f',
      DEC1 = ',.1f',
      INT = ',.0f',
      SI = ',.3s',
      DATE = '%b %d, %Y' // https://github.com/d3/d3-time-format#locale_format

const FORMATS = {
  DEC: d3.format(DEC),
  DEC1: d3.format(DEC1),
  INT: d3.format(INT),
  SI: d3.format(SI),
  SMART: function (num) {
    if (num < 100) {
      return d3.format(DEC)(num)
    } else if (num < 10000) {
      return d3.format(INT)(num)
    } else {
      return d3.format(SI)(num)
    }
  },
  DATE: d3.timeFormat(DATE),
}

const SCALES = {
  [LINEAR_SCALE]: d3.scaleLinear(),
  [LOG_SCALE]: d3.scaleLog(),
}

class Chart extends React.Component {
  // chart height is the entire chart, including indicators, but excluding margins
  // plot height is the height of the price chart only, also excluding margins
  _drawAxes() {
    // x axis
    this.xScale = techan.scale.financetime()
      .range([0, this.chartWidth])
      .outerPadding(1)

    this.xAxis = d3.axisBottom(this.xScale)
      .tickSizeOuter(0)

    this.svg.append('g')
      .attr('class', 'x axis')
      .attr('transform', `translate(0, ${this.chartHeight})`) 

    // x grid
    this.xGrid = d3.axisBottom(this.xScale)
      .ticks(4) // FIXME: this draws quarterly lines with daily data, what about intraday/weekly/monthly??
      .tickFormat(() => null)
      .tickSizeInner(-this.plotHeight)
      .tickSizeOuter(-this.plotHeight)

    this.svg.append('g')
      .attr('class', 'x grid')
      .attr('transform', `translate(0, ${this.plotHeight})`)

    // y axis
    this.yScale = SCALES[this.props.scale]
      .range([this.plotHeight, 0])

    this.yAxis = d3.axisLeft(this.yScale)
      .tickFormat(FORMATS.SMART)

    this.svg.append('g')
      .attr('class', 'y axis')

    this.yAxisRight = d3.axisRight(this.yScale)
      .tickSizeOuter(0)
      .tickFormat(FORMATS.SMART)

    this.svg.append('g')
      .attr('class', 'y axis right')
      .attr('transform', `translate(${this.chartWidth}, 0)`)

    // y grid
    this.yGrid = d3.axisLeft(this.yScale)
      .tickFormat(() => null)
      .tickSizeInner(-this.chartWidth)
      .tickSizeOuter(-this.chartWidth)

    this.svg.append('g')
      .attr('class', 'y grid')
  }
}
/* relevant styles */

.techan-chart {
  .grid path.domain,
  .axis path.domain {
    stroke: $axis-color;
  }
  .axis .tick line {
    stroke: $axis-tick-color;
  }

  .grid .tick line {
    stroke: $grid-color;
    opacity: $grid-opacity;
  }
}

@icastillejogomez
Copy link
Author

Thank you so much # @briancappello for the fast answer

@briancappello
Copy link
Contributor

No problem. I updated the code a bit to add the missing constants and explain the difference between chartHeight and plotHeight, hope it's helpful :)

@briancappello
Copy link
Contributor

Are you by chance using React in your app? (I'd like to open source the charting component but I don't really know to package it, and I don't really have time to figure it for the forseeable future)

@icastillejogomez
Copy link
Author

icastillejogomez commented Oct 1, 2018

Yes I’m using React in my app. Did you wrapped techan with React?

@GreatGranPa
Copy link

GreatGranPa commented Oct 1, 2018 via email

@briancappello
Copy link
Contributor

Yea, or at least I tried haha. It mostly works

I created a gist here with the meat of the code (I'm sure I missed a few imports). I also started a refactor on the indicator support a long while ago that I'm pretty sure is in a broken state but that exists too

@briancappello
Copy link
Contributor

You can consider that code as MIT licensed, and if you let me know what imports I missed, i'd be happy to add the files

@icastillejogomez
Copy link
Author

What exactly do you need to export the component @briancappello ? I don't understand your trouble

@icastillejogomez
Copy link
Author

icastillejogomez commented Oct 3, 2018

@briancappello I can't show the chart grid 😢

My chart looks like:

captura de pantalla 2018-10-03 a las 3 17 54

Here's my legacy code, can you help me?

_drawCandleStickChart () {
    var dim = {
        width: 1300, height: 650,
        margin: { top: 20, right: 50, bottom: 30, left: 50 },
        ohlc: { height: 600 },
        indicator: { height: 85, padding: 5 }
    }
    dim.plot = {
        width: dim.width - dim.margin.left - dim.margin.right,
        height: dim.height - dim.margin.top - dim.margin.bottom
    }
    dim.indicator.top = dim.ohlc.height+dim.indicator.padding
    dim.indicator.bottom = dim.indicator.top+dim.indicator.height+dim.indicator.padding

    var indicatorTop = d3.scaleLinear().range([dim.indicator.top, dim.indicator.bottom])

    const parseDate = d3.timeParse('%d-%b-%y')

    var zoom = d3.zoom().on("zoom", zoomed)

    var x = techan.scale.financetime().range([0, dim.plot.width])
    var y = d3.scaleLinear().range([dim.ohlc.height, 0])

    // x grid
    var xGrid = d3.axisBottom(x)
      .ticks(4) // FIXME: this draws quarterly lines with daily data, what about intraday/weekly/monthly??
      .tickFormat(() => null)
      .tickSizeInner(-dim.plot.height)
      .tickSizeOuter(-dim.plot.height)

    // y grid
    var yGrid = d3.axisLeft(y)
      .tickFormat(() => null)
      .tickSizeInner(-dim.width)
      .tickSizeOuter(-dim.width)

    var yInit 
    var zoomableInit

    var yVolume = d3.scaleLinear().range([y(0), y(0.2)])
      
    var candlestick = techan.plot.candlestick()
      .xScale(x)
      .yScale(y)

    var tradearrow = techan.plot.tradearrow()
      .xScale(x)
      .yScale(y)
      .y(function(d) {
          // Display the buy and sell arrows a bit above and below the price, so the price is still visible
          if(d.type === 'buy') return y(d.low)+5
          if(d.type === 'sell') return y(d.high)-5
          else return y(d.price)
      })

    var sma0 = techan.plot.sma()
      .xScale(x)
      .yScale(y)

    var sma1 = techan.plot.sma()
      .xScale(x)
      .yScale(y)

    var ema2 = techan.plot.ema()
      .xScale(x)
      .yScale(y)

    var volume = techan.plot.volume()
      .accessor(candlestick.accessor())   // Set the accessor to a ohlc accessor so we get highlighted bars
      .xScale(x)
      .yScale(yVolume)


    var trendline = techan.plot.trendline()
      .xScale(x)
      .yScale(y)

    var supstance = techan.plot.supstance()
      .xScale(x)
      .yScale(y)

    var xAxis = d3.axisBottom(x)

    var timeAnnotation = techan.plot.axisannotation()
      .axis(xAxis)
      .orient('bottom')
      .format(d3.timeFormat('%Y-%m-%d'))
      .width(65)
      .translate([0, dim.plot.height])

    var yAxis = d3.axisRight(y)

    var ohlcAnnotation = techan.plot.axisannotation()
      .axis(yAxis)
      .orient('right')
      .format(d3.format(',.2f'))
      .translate([x(1), 0])

    var closeAnnotation = techan.plot.axisannotation()
      .axis(yAxis)
      .orient('right')
      .accessor(candlestick.accessor())
      .format(d3.format(',.2f'))
      .translate([x(1), 0])

    var volumeAxis = d3.axisLeft(yVolume)
      .ticks(3)
      .tickFormat(d3.format(",.3s"))

    var volumeAnnotation = techan.plot.axisannotation()
      .axis(volumeAxis)
      .orient("right")
      .width(35)

    var ohlcCrosshair = techan.plot.crosshair()
      .xScale(timeAnnotation.axis().scale())
      .yScale(ohlcAnnotation.axis().scale())
      .xAnnotation(timeAnnotation)
      .yAnnotation([ohlcAnnotation, /* percentAnnotation, */ volumeAnnotation])
      .verticalWireRange([0, dim.plot.height])

    var svg = d3.select("#chart").append("svg")
      .attr("width", dim.width)
      .attr("height", dim.height)

    var defs = svg.append("defs")


    defs.append("clipPath")
      .attr("id", "ohlcClip")
      .append("rect")
      .attr("x", 0)
      .attr("y", 0)
      .attr("width", dim.plot.width)
      .attr("height", dim.ohlc.height)

    defs.selectAll("indicatorClip").data([0, 1])
      .enter()
      .append("clipPath")
      .attr("id", function(d, i) { return "indicatorClip-" + i })
      .append("rect")
      .attr("x", 0)
      .attr("y", function(d, i) { return indicatorTop(i) })
      .attr("width", dim.plot.width)
      .attr("height", dim.indicator.height)


    svg = svg.append("g").attr("transform", "translate(" + dim.margin.left + "," + dim.margin.top + ")")


    svg.append('text')
      .attr("class", "symbol")
      .attr("x", 20)
      .text("Apple Inc. (AAPL)")

    // Open
    svg.append('text')
      .attr("class", "value-section")
      .attr("x", 20)
      .attr("y", 20)
      .text("Open");
    const open_text = svg.append('text')
      .attr("class", "value open")
      .attr("x", 80)
      .attr("y", 20)
      .text("33.20");

    // High
    svg.append('text')
      .attr("class", "value-section")
      .attr("x", 20)
      .attr("y", 40)
      .text("High");
    const high_text = svg.append('text')
      .attr("class", "value high")
      .attr("x", 80)
      .attr("y", 40)
      .text("33.20");

    // Low
    svg.append('text')
      .attr("class", "value-section")
      .attr("x", 20)
      .attr("y", 60)
      .text("Low");
    const low_text = svg.append('text')
      .attr("class", "value low")
      .attr("x", 80)
      .attr("y", 60)
      .text("33.20");

    // Close
    svg.append('text')
      .attr("class", "value-section")
      .attr("x", 20)
      .attr("y", 80)
      .text("Close");
    const close_text = svg.append('text')
      .attr("class", "value close")
      .attr("x", 80)
      .attr("y", 80)
      .text("33.20");

    // Volume
    svg.append('text')
      .attr("class", "value-section")
      .attr("x", 20)
      .attr("y", 100)
      .text("Volume");
    const volume_text = svg.append('text')
      .attr("class", "value volume")
      .attr("x", 80)
      .attr("y", 100)
      .text("33.20");

    svg.append("g")
      .attr("class", "x axis")
      .attr("transform", "translate(0," + dim.plot.height + ")")

    

    var ohlcSelection = svg.append("g")
      .attr("class", "ohlc")
      .attr("transform", "translate(0,0)")

    ohlcSelection.append("g")
      .attr("class", "axis")
      .attr("transform", "translate(" + x(1) + ",0)")
      .append("text")
      .attr("transform", "rotate(-90)")
      .attr("y", -12)
      .attr("dy", ".71em")
      .style("text-anchor", "end")
      .text("Precio ($)")

    ohlcSelection.append('g')
      .attr('class', 'x grid')
      .attr('transform', `translate(0, ${dim.plot.height})`)

    ohlcSelection.append("g")
      .attr("class", "close annotation up")

    ohlcSelection.append("g")
      .attr("class", "volume")
      .attr("clip-path", "url(#ohlcClip)")

    ohlcSelection.append("g")
      .attr("class", "candlestick")
      .attr("clip-path", "url(#ohlcClip)")

    ohlcSelection.append("g")
      .attr("class", "indicator sma ma-0")
      .attr("clip-path", "url(#ohlcClip)")

    ohlcSelection.append("g")
      .attr("class", "indicator sma ma-1")
      .attr("clip-path", "url(#ohlcClip)")

    ohlcSelection.append("g")
      .attr("class", "indicator ema ma-2")
      .attr("clip-path", "url(#ohlcClip)")

    ohlcSelection.append("g")
      .attr("class", "percent axis")

    ohlcSelection.append("g")
      .attr("class", "volume axis")

    // Add trendlines and other interactions last to be above zoom pane
    svg.append('g')
      .attr("class", "crosshair ohlc")

    svg.append("g")
      .attr("class", "tradearrow")
      .attr("clip-path", "url(#ohlcClip)")

    svg.append("g")
      .attr("class", "trendlines analysis")
      .attr("clip-path", "url(#ohlcClip)")
    svg.append("g")
      .attr("class", "supstances analysis")
      .attr("clip-path", "url(#ohlcClip)")


    // request and draw
    var accessor = candlestick.accessor()
    var indicatorPreRoll = 33  // Don't show where indicators don't have data

    const data = owndata.map(function(d) {
      return {
          date: parseDate(d.Date),
          open: +d.Open,
          high: +d.High,
          low: +d.Low,
          close: +d.Close,
          volume: +d.Volume
      }
    }).sort(function(a, b) { return d3.ascending(accessor.d(a), accessor.d(b)) })

    x.domain(techan.scale.plot.time(data).domain())
    y.domain(techan.scale.plot.ohlc(data.slice(indicatorPreRoll)).domain())
    yVolume.domain(techan.scale.plot.volume(data).domain())

    var trendlineData = [
        { start: { date: new Date(2014, 2, 11), value: 72.50 }, end: { date: new Date(2014, 5, 9), value: 63.34 } },
        { start: { date: new Date(2013, 10, 21), value: 43 }, end: { date: new Date(2014, 2, 17), value: 70.50 } }
    ]

    var supstanceData = [
        { start: new Date(2014, 2, 11), end: new Date(2014, 5, 9), value: 63.64 },
        { start: new Date(2013, 10, 21), end: new Date(2014, 2, 17), value: 55.50 }
    ]

    var trades = [
        { date: data[67].date, type: "buy", price: data[67].low, low: data[67].low, high: data[67].high },
        { date: data[100].date, type: "sell", price: data[100].high, low: data[100].low, high: data[100].high },
        { date: data[130].date, type: "buy", price: data[130].low, low: data[130].low, high: data[130].high },
        { date: data[170].date, type: "sell", price: data[170].low, low: data[170].low, high: data[170].high }
    ]

    svg.select("g.candlestick").datum(data).call(candlestick)
    svg.select("g.close.annotation").datum([data[data.length-1]]).call(closeAnnotation)
    svg.select("g.volume").datum(data).call(volume)
    svg.select("g.sma.ma-0").datum(techan.indicator.sma().period(10)(data)).call(sma0)
    svg.select("g.sma.ma-1").datum(techan.indicator.sma().period(20)(data)).call(sma1)
    svg.select("g.ema.ma-2").datum(techan.indicator.ema().period(50)(data)).call(ema2)


    svg.select("g.crosshair.ohlc").call(ohlcCrosshair).call(zoom)
    svg.select("g.trendlines").datum(trendlineData).call(trendline).call(trendline.drag)
    svg.select("g.supstances").datum(supstanceData).call(supstance).call(supstance.drag)

    svg.select("g.tradearrow").datum(trades).call(tradearrow)

    // Stash for zooming
    zoomableInit = x.zoomable().domain([indicatorPreRoll, data.length]).copy() // Zoom in a little to hide indicator preroll
    yInit = y.copy()

    draw()

    svg.on("mousemove", mousemove);

    // End request and draw

    function reset() {
      zoom.scale(1)
      zoom.translate([0,0])
      draw()
    }

    function zoomed() {
      x.zoomable().domain(d3.event.transform.rescaleX(zoomableInit).domain())
      y.domain(d3.event.transform.rescaleY(yInit).domain())

      draw()
    }

    function draw() {
      svg.select("g.x.axis").call(xAxis)
      svg.select("g.ohlc .axis").call(yAxis)
      svg.select("g.volume.axis").call(volumeAxis)

      // We know the data does not change, a simple refresh that does not perform data joins will suffice.
      svg.select("g.candlestick").call(candlestick.refresh)
      svg.select("g.close.annotation").call(closeAnnotation.refresh)
      svg.select("g.volume").call(volume.refresh)
      svg.select("g .sma.ma-0").call(sma0.refresh)
      svg.select("g .sma.ma-1").call(sma1.refresh)
      svg.select("g .ema.ma-2").call(ema2.refresh)
      svg.select("g.crosshair.ohlc").call(ohlcCrosshair.refresh)
      svg.select("g.trendlines").call(trendline.refresh)
      svg.select("g.supstances").call(supstance.refresh)
      svg.select("g.tradearrow").call(tradearrow.refresh)
    }

    const bisectDate = d3.bisector(function(d) { return d.date; }).left;

    function mousemove() {
      let mouseX    = x.invert(d3.mouse(this)[0]);
      let index     = bisectDate(data, mouseX, 1);
      let entry     = data[index];
      let open      = entry.open;
      let high      = entry.high;
      let low       = entry.low;
      let close     = entry.close;
      let volume    = entry.volume;
      open_text.text( open );
      high_text.text( high );
      low_text.text( low );
      close_text.text( close );
      volume_text.text( volume );
    }

  }

@icastillejogomez
Copy link
Author

@briancappello Hi Brian!! I need help with chart grid 😢 I tried to implement your solution but I could not did it. Can you show me one picture of your chart grid to check if it is what I need. Or can you quickly check my code? Maybe you can see something wrong fast.

Thank you so much for all this help 😄

@briancappello
Copy link
Contributor

briancappello commented Oct 11, 2018 via email

@icastillejogomez
Copy link
Author

Don't worry!!! 😄 Thank you @briancappello I will be waiting for you next week

@icastillejogomez
Copy link
Author

Tomorrow I'll try this code: https://bl.ocks.org/d3noob/c506ac45617cf9ed39337f99f8511218

@briancappello
Copy link
Contributor

briancappello commented Oct 19, 2018

Hey @icastillejogomez, sorry for the delay (I ended up having to fix some of my backend code before I could get this running in order to take a screenshot, woops). I'm gonna spend some time this weekend to see if I can't get my chart code released as a library, hopefully it goes well :)

chart

full size

I skimmed your code, didn't notice anything immediately obviously wrong, but if that screenshot is what you're looking for I can take a closer look

@icastillejogomez
Copy link
Author

Thank you so much @briancappello I now showing X axis grid but I can't display the Y axis grid. For other side... it is possible display more than two SMA in chart? I coded the for loop and work out for one and two SMA but with three SMA crash. I suppose I have a bug.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants