constructor(props) {
     super(props);
     this.x = scaleTime();
     this.y = scaleLinear();
     this.x2 = scaleTime();
     this.y2 = scaleLinear();
     this.margin = { top: 0, right: 40, bottom: 100, left: 0 };
     this.margin2 = { right: 10, bottom: 20, left: 0 };
     this.state = {
         brush: false,
         selection: null,
         cursorData: null,
         cursorX: 0,
         cursorVisible: false,
     };
     this.getDefaultFocusDomain = this.getDefaultFocusDomain.bind(this);
     this.getCursorState = this.getCursorState.bind(this);
     this.handleWheel = this.handleWheel.bind(this);
     this.handleMouseMove = this.handleMouseMove.bind(this);
     this.handleMouseOut = this.handleMouseOut.bind(this);
     this.handleBrushMount = this.handleBrushMount.bind(this);
     this.handleBrushStart = this.handleBrushStart.bind(this);
     this.handleBrush = this.handleBrush.bind(this);
     this.handleBrushEnd = this.handleBrushEnd.bind(this);
     this.updateContextDomains = this.updateContextDomains.bind(this);
     this.updateFocusDomain = this.updateFocusDomain.bind(this);
     this.updateCursor = this.updateCursor.bind(this);
     this.updateDimension = this.updateDimension.bind(this);
     this.updateD3(props);
 }
    describe('without discontinuities', () => {

        var range = [0, 100];
        var start = new Date(2015, 0, 18); // sunday
        var end = new Date(2015, 0, 28); // wednesday

        var referenceScale = scaleTime()
                .domain([start, end])
                .range(range);

        var dateTime = discontinuous(scaleTime())
            .domain([start, end])
            .range(range);

        it('should match the scale functionality of a d3 time scale', () => {
            var date = new Date(2015, 0, 19);
            expect(dateTime(date)).toEqual(referenceScale(date));

            date = new Date(2015, 0, 25);
            expect(dateTime(date)).toEqual(referenceScale(date));

            expect(dateTime(start)).toEqual(referenceScale(start));
            expect(dateTime(end)).toEqual(referenceScale(end));
        });

        it('should match the invert functionality of a d3 time scale', () => {
            expect(dateTime.invert(0)).toEqual(referenceScale.invert(0));
            expect(dateTime.invert(50)).toEqual(referenceScale.invert(50));
            expect(dateTime.invert(100)).toEqual(referenceScale.invert(100));
        });
    });
function setUpCommonTimeAxis() {
    sharedTimeScale = scaleTime().domain(timelineSpan).range([0, timelineSize.width]);
    sharedTimeScale0 = scaleTime().domain(timelineSpan).range([0, timelineSize.width]);

    const customTimeFormat = makeTimeTickFormat(".%L", ":%S", "%_I:%M", "%_I %p", "%b %_d", "%b %_d", "%b", "%Y"),
        customTimeFormatDayNames = makeTimeTickFormat(" ", " ", " ", " ", "%a", "%a", "%a", " ");

    timelineXAxisMain = makeTimelineAxis(sharedTimeScale, customTimeFormat, -timelineSize.height, 6);
    timelineXAxisDayNames = makeTimelineAxis(sharedTimeScale, customTimeFormatDayNames, -timelineSize.height, 18);
    timelineXAxisDays = makeTimelineAxis(sharedTimeScale, "", -timelineSize.height, 0).ticks(timeDay.every(1));
    timelineXAxisWeeks = makeTimelineAxis(sharedTimeScale, "", -timelineSize.height, 0).ticks(timeWeek.every(1));
    timelineXAxisHidden = makeTimelineAxis(sharedTimeScale, "", 0, 0).ticks(timeYear.every(1));
}
function addFeed(feed) {
    dataFeeds.push(feed);

    const feedIndex = Object.keys(feedIndices).length,
        newTimelineSpan = extent(feed.data, d => d.timestamp),
        yAxis = select("#timelineRootSVG").append("g");

    select('#timelineInner').append('g').attr('id', 'data' + feed.feedInfo.feedId);
    select('#timelineInner').append('g').attr('id', 'trend' + feed.feedInfo.feedId);
    select('#timelineInner').append('g').attr('id', 'baseLine' + feed.feedInfo.feedId);
    select('#timelineOuter').append('g')
        .attr('id', 'label' + feed.feedInfo.feedId)
        .attr('pointer-events', 'auto');

    feedIndices[feed.feedInfo.feedId] = feedIndex;
    timelineSpan = dataFeeds.length === 0 ? newTimelineSpan : [Math.min(timelineSpan[0], newTimelineSpan[0]), Math.max(timelineSpan[1], newTimelineSpan[1])];
    sharedTimeScale0 = scaleTime().domain(timelineSpan).range([0, timelineSize.width]);
    resetTimelineSpan(timelineSpan);

    feedHeight = (timelineSize.height - feedPadding) / Object.keys(feedIndices).length - feedPadding;

    yAxis
        .attr("id", "yAxis" + feed.feedInfo.feedId)
        .attr("pointer-events", "none");

    yAxis.append("g")
        .attr("class", "axis-y");

    updateFeed(feed);
}
        it('should clamp the values supplied', () => {
            var dateTime = discontinuous(scaleTime())
                .discontinuityProvider(skipWeekends())
                .domain([start, end]);

            expect(dateTime.domain()[0]).toEqual(startOfWeek);
            expect(dateTime.domain()[1]).toEqual(endOfWeek);
        });
 constructor(props) {
     super(props);
     this.x = scaleTime();
     this.y = scaleLinear();
     this.margin = { top: 0, right: 0, bottom: 60, left: 0 };
     this.getDefaultDomain = this.getDefaultDomain.bind(this);
     this.updateDomains = this.updateDomains.bind(this);
     this.updateDimension = this.updateDimension.bind(this);
     this.updateD3(props);
 }
Exemple #7
0
  xScale: computed('data.[]', 'xProp', 'timeseries', 'yAxisOffset', function() {
    const xProp = this.xProp;
    const scale = this.timeseries ? d3Scale.scaleTime() : d3Scale.scaleLinear();
    const data = this.data;

    const domain = data.length ? d3Array.extent(this.data, d => d[xProp]) : [0, 1];

    scale.rangeRound([10, this.yAxisOffset]).domain(domain);

    return scale;
  }),
Exemple #8
0
  buildChartScales() {

    const tasks = this.props.tasks;

    const dateElements = this.getDateElements(tasks);
    const maxDate = max(dateElements);
    const minDate = min(dateElements);

    this.xScale = scaleTime().domain([minDate,maxDate]).range([0,this.width]);
    this.yScale = scaleLinear().domain([0,tasks.length]).range([0,this.height]);
  }
        it('should support arguments being passed to ticks', () => {
            var start = new Date(2015, 0, 9); // friday
            var end = new Date(2015, 0, 12); // monday

            var dateTime = discontinuous(scaleTime())
                .discontinuityProvider(skipWeekends())
                .domain([start, end]);

            var ticks = dateTime.ticks(100);
            expect(ticks.length).toEqual(25);
        });
        it('should ensure ticks are not within discontinuities', () => {
            var start = new Date(2015, 0, 9); // friday
            var end = new Date(2015, 0, 12); // monday

            var dateTime = discontinuous(scaleTime())
                .discontinuityProvider(skipWeekends())
                .domain([start, end]);

            var ticks = dateTime.ticks();
            expect(ticks.length).toEqual(5);
        });
        /**
         * Creates the x, y and color scales of the chart
         * @private
         */
        function buildScales() {
            xScale = d3Scale.scaleTime()
                .domain(d3Array.extent(dataByDate, ({date}) => date))
                .rangeRound([0, chartWidth]);

            yScale = d3Scale.scaleLinear()
                .domain([0, getMaxValueByDate()])
                .rangeRound([chartHeight, 0])
                .nice();

            categoryColorMap =  order.reduce((memo, topic, index) => (
                assign({}, memo, {[topic]: colorSchema[index]})
            ), {});
        }
Exemple #12
0
  xScale: computed('data.[]', 'xProp', 'timeseries', 'yAxisOffset', function() {
    const xProp = this.xProp;
    const scale = this.timeseries ? d3Scale.scaleTime() : d3Scale.scaleLinear();
    const data = this.data;

    const [low, high] = d3Array.extent(data, d => d[xProp]);
    const minLow = moment(high)
      .subtract(5, 'minutes')
      .toDate();

    const extent = data.length ? [Math.min(low, minLow), high] : [minLow, new Date()];
    scale.rangeRound([10, this.yAxisOffset]).domain(extent);

    return scale;
  }),
    it('should support copy', () => {
        var start = new Date(2015, 0, 8); // thursday
        var end = new Date(2015, 0, 15); // thursday

        var dateTime = discontinuous(scaleTime())
            .discontinuityProvider(skipWeekends())
            .range([0, 100])
            .domain([start, end]);

        var clone = dateTime.copy();

        expect(clone.discontinuityProvider()).toEqual(dateTime.discontinuityProvider());
        expect(clone.range()[0]).toEqual(0);
        expect(clone.range()[1]).toEqual(100);
        expect(clone.domain()[0]).toEqual(start);
        expect(clone.domain()[1]).toEqual(end);
    });
 AreaChartStackedComponent.prototype.getXScale = function (domain, width) {
     var scale;
     if (this.scaleType === 'time') {
         scale = scaleTime();
     }
     else if (this.scaleType === 'linear') {
         scale = scaleLinear();
     }
     else if (this.scaleType === 'ordinal') {
         scale = scalePoint()
             .padding(0.1);
     }
     scale
         .range([0, width])
         .domain(domain);
     return this.roundDomains ? scale.nice() : scale;
 };
Exemple #15
0
 PolarChartComponent.prototype.getXScale = function (domain, width) {
     switch (this.scaleType) {
         case 'time':
             return scaleTime()
                 .range([0, width])
                 .domain(domain);
         case 'linear':
             var scale = scaleLinear()
                 .range([0, width])
                 .domain(domain);
             return this.roundDomains ? scale.nice() : scale;
         default:
             return scalePoint()
                 .range([0, width - twoPI / domain.length])
                 .padding(0)
                 .domain(domain);
     }
 };
Exemple #16
0
  render() {
    let { data, type, width, ratio } = this.props;
    return (
      <div>

        <FormattedMessage {...messages.header} />
        <ChartCanvas ratio={ratio} width={width} height={400}
                     margin={{ left: 50, right: 50, top: 10, bottom: 30 }}
                     seriesName="MSFT"
                     data={data} type={type}
                     xAccessor={d => d.date} xScale={scaleTime()}
                     xExtents={[new Date(2011, 0, 1), new Date(2013, 0, 2)]}>
          <Chart id={0} yExtents={d => d.close}>
            <XAxis axisAt="bottom" orient="bottom" ticks={6}/>
            <YAxis axisAt="left" orient="left"/>
            <AreaSeries yAccessor={(d) => d.close}/>
          </Chart>
        </ChartCanvas>
      </div>
    );
  }
const PatientViewBannerRiskChart = ({ riskAssessments, onClickHandler=function(){}}) => {
  // This is > 1 because if there is only one assessment a chart doesn't make a whole lot of sense.
  if (riskAssessments && riskAssessments.length > 1) {

    riskAssessments = riskAssessments.sort((a,b) => new Date(a.datetime) - new Date(b.datetime));
    let timeScale = scaleTime()
      .domain(extent(riskAssessments, (r) => new Date(r.datetime)))
      .range([1,99]);
    let riskScale = scaleLinear().domain([4,0]).range([1,9]);
    let areaGenerator = area().y0(100);
    // Define a function to project the riskAssessments into the svg's coordinate system
    // This will make building diagram easier
    // Note ,if you need more data from the riskAssessments the indexes line up
    let projection = (d) => [timeScale(new Date(d.datetime)), riskScale(d.value)];
    let hitTargets = voronoi().extent([[-1, -1], [101, 21]]);


    let projectedData = riskAssessments.map(projection);
    let hitTargetPolys = hitTargets.polygons(projectedData);
    return (
      <div className='patient-view-banner-risk-chart'>
        <svg viewBox='0 0 100 10' width='100%' height='100%'>
          <path d={areaGenerator(projectedData)} className='path'/>
          {projectedData.map((ra, i) =>
            <g key={i}>
              <circle cx={ra[0]} cy={ra[1]} r={0.6} className='data-point'/>
              <path d={line()(hitTargetPolys[i])} opacity={0} onClick={() => onClickHandler(i)} className='click-target'/>
            </g>
          )}
        </svg>
      </div>
    );
  }
  return (
    <div>

    </div>
  );
};
Exemple #18
0
export function getScale(domain, range, scaleType, roundDomains) {
    var scale;
    if (scaleType === 'time') {
        scale = scaleTime()
            .range(range)
            .domain(domain);
    }
    else if (scaleType === 'linear') {
        scale = scaleLinear()
            .range(range)
            .domain(domain);
        if (roundDomains) {
            scale = scale.nice();
        }
    }
    else if (scaleType === 'ordinal') {
        scale = scalePoint()
            .range([range[0], range[1]])
            .domain(domain);
    }
    return scale;
}
Exemple #19
0
    render() {
        const { series, height = 500, width = 800 } = this.props;

        const x = scaleTime().range([0, width]);
        const y = scaleLinear().range([0, height]);
        const xAxis = axisBottom(x);
        const yAxis = axisLeft(y);
        const path = line()
            .x(d => x(d.time))
            .y(d => y(d.price));
        x.domain(extent(series, d => d.time));
        y.domain(extent(series, d => d.price));

        return (
            <svg width={width} height={height}>
                <g className="chart">
                    <Line line={path} data={series}/>
                    <Axis axis={yAxis}/>
                    <Axis axis={xAxis}/>
                </g>
            </svg>
        );
    }
 constructor(props) {
   super(props);
   const { ticks, lowestPrice, highestPrice, tradingHours } = data;
   this.timeScale =
     d3Scale
       .scaleTime()
       .domain(tradingHours.map( t => t * 1000))
       .range([0, defaultWidth]);
   this.priceScale =
     d3Scale
       .scaleLinear()
       .domain([lowestPrice, highestPrice])
       // reverse bacause the origin point of d3 svg is top left
       .range([0, defaultStockChartHeight].reverse());
   this.volumes = ticks.map(t => t.volume);
   this.volumeScale =
     d3Scale
     .scaleLinear()
     .domain([Math.min(...this.volumes), Math.max(...this.volumes)])
     .range([0, defaultVolumeChartHeight]);
   const chartPercent = (ticks[ticks.length - 1].time - tradingHours[0]) / (tradingHours[1] - tradingHours[0]);
   this.barWidth =  chartPercent * defaultWidth / this.volumes.length;
 }
Exemple #21
0
const viewData = (model, address) => {
  const {series, width, height, tooltipWidth,
    scrubber, xhairAt, recipeStart, recipeEnd, markers} = model;

  const lines = series.lines;

  // Read out series class into array.
  const extentX = [series.min, series.max];

  const scrubberMaxX = width - 12;
  const scrubberX = clamp(scrubber.coords[0], 0, scrubberMaxX);
  const isDragging = scrubber.isDragging;

  // Calculate dimensions
  // We need to have one row per group of 2 lines
  const nTooltipRow = Math.floor(lines.length / 2);
  const tooltipHeight = (nTooltipRow * READOUT_HEIGHT) + (TOOLTIP_PADDING * 2);
  const plotWidth = calcPlotWidth(extentX);
  const plotHeight = calcPlotHeight(height, tooltipHeight);
  const svgHeight = calcSvgHeight(height);
  const tickTop = calcXhairTickTop(height, tooltipHeight);

  // Calculate scales
  const x = scaleTime()
    .domain(extentX)
    .range([0, plotWidth]);

  const xhairRatioToXhairX = scaleLinear()
    .domain(RATIO_DOMAIN)
    .range([0, width])
    .clamp(true);

  const scrubberToPlotX = scaleLinear()
    .domain([0, scrubberMaxX])
    // Translate up to the point that the right side of the plot is adjacent
    // to the right side of the viewport.
    .range([0, plotWidth - width])
    .clamp(true);

  const plotX = scrubberToPlotX(scrubberX);

  const xhairX = xhairRatioToXhairX(xhairAt);
  const tooltipX = calcTooltipX(xhairX, width, tooltipWidth);

  // Calculate xhair absolute position by adding crosshair position in viewport
  // to plot offset. Then use absolute coord in chart viewport to get inverse
  // value (time) under xhair.
  const xhairTime = x.invert(plotX + xhairX);

  const children = lines.map(line => viewLine(line, address, x, plotHeight));

  const axis = renderAxis(x, svgHeight);
  children.push(axis);

  const userMarkers = renderUserMarkers(markers, x, svgHeight);
  children.push(userMarkers);

  if (recipeStart) {
    const recipeStartMarker = renderAxisMarker(
      x(recipeStart),
      svgHeight,
      localize('Recipe Started')
    );

    children.push(recipeStartMarker);
  }

  if (recipeEnd) {
    const recipeEndMarker = renderAxisMarker(
      x(recipeEnd),
      svgHeight,
      localize('Recipe Ended')
    );
    children.push(recipeEndMarker);
  }

  const chartSvg = svg({
    width: plotWidth,
    height: svgHeight,
    className: 'chart-svg',
    style: {
      // Translate SVG to move the visible portion of the plot in response
      // to scrubber.
      transform: translateXY(-1 * plotX, 0)
    }
  }, children);

  const readouts = series
    .asGroups()
    .map(group => renderReadout(group, xhairTime));

  return html.div({
    className: 'chart split-view-content',
    onMouseMove: event => {
      const [mouseX, mouseY] = calcRelativeMousePos(
        event.currentTarget,
        event.clientX, event.clientY
      );

      const xhairAt = xhairRatioToXhairX.invert(mouseX);
      address(MoveXhair(xhairAt));
    },
    onTouchStart: event => {
      // Prevent from becoming a click event.
      event.preventDefault();
    },
    onTouchMove: event => {
      event.preventDefault();
      const changedTouches = event.changedTouches;
      if (changedTouches.length) {
        // @TODO it might be better to find the common midpoint between multiple
        // touches if touches > 1.
        const touch = changedTouches.item(0);
        const coords = calcRelativeMousePos(
          event.currentTarget,
          touch.clientX,
          touch.clientY
        );
        const xhairAt = xhairRatioToXhairX.invert(coords[0]);
        address(MoveXhair(xhairAt));
      }
    },
    onResize: onWindow(address, () => {
      return Resize(
        calcChartWidth(window.innerWidth),
        calcChartHeight(window.innerHeight)
      );
    }),
    style: {
      width: px(width),
      height: px(height)
    }
  }, [
    chartSvg,
    html.div({
      className: 'chart-xhair',
      style: {
        transform: translateXY(xhairX, 0)
      }
    }),
    html.div({
      className: 'chart-xhair--tick',
      style: {
        transform: translateXY(xhairX, tickTop)
      }
    }),
    html.div({
      className: 'chart-tooltip',
      style: {
        width: px(tooltipWidth),
        height: px(tooltipHeight),
        transform: translateXY(tooltipX, 0)
      }
    }, [
      html.div({
        className: 'chart-timestamp'
      }, [
        html.div({
          className: 'chart-timestamp--time'
        }, [
          // Convert seconds to ms for formatTime
          formatTime(xhairTime)
        ]),
        html.div({
          className: 'chart-timestamp--day'
        }, [
          // Convert seconds to ms for formatTime
          formatDay(xhairTime)
        ]),
      ]),
      html.div({
        className: 'chart-readouts'
      }, readouts)
    ]),
    html.div({
      className: classed({
        'chart-scrubber': true,
        'chart-scrubber--active': isDragging
      }),
      onMouseDown: onScrubberMouseDown(address),
      onMouseMove: onScrubberMouseMove(address),
      onMouseUp: onScrubberMouseUp(address),
      onTouchStart: onScrubberTouchStart(address),
      onTouchMove: onScrubberTouchMove(address),
      onTouchEnd: onScrubberTouchEnd(address)
    }, [
      html.div({
        className: 'chart-scrubber--backing'
      }),
      html.div({
        className: 'chart-progress',
        style: {
          width: px(scrubberX)
        }
      }),
      html.div({
        className: classed({
          'chart-handle': true,
          'chart-handle--dragging': isDragging
        }),
        style: {
          transform: translateXY(scrubberX, 0)
        }
      }, [
        html.div({
          className: 'chart-handle--cap'
        }),
        html.div({
          className: 'chart-handle--line'
        })
      ])
    ])
  ]);
}
  render() {
    const { ticks, lowestPrice, highestPrice, tradingHours } = data;
    const tickCounts = 3;
    const timeScale =
      d3Scale
        .scaleTime()
        .domain(tradingHours.map( t => t * 1000))
        .range([0, deviceWidth]);
    const priceScale =
      d3Scale
        .scaleLinear()
        .domain([lowestPrice, highestPrice])
        .range([0, defaultStockChartHeight].reverse());
    const lineFunction =
      d3Shape
        .line()
        .x(d => timeScale(d.time * 1000))
        .y(d => priceScale(d.price));
    const areaFunction =
      d3Shape
        .area()
        .x(d => timeScale(d.time * 1000))
        .y(d => priceScale(d.price))
        .y1(() => priceScale(lowestPrice));
    const priceTicks = d3Array.ticks(lowestPrice, highestPrice, tickCounts);


    const adjustPriceTicks = this.getExclusiveTicks(lowestPrice, highestPrice, tickCounts);
    const adjustPriceScale =
      d3Scale
      .scaleLinear()
      .domain([adjustPriceTicks[0], adjustPriceTicks[adjustPriceTicks.length - 1]])
      .range([0, defaultStockChartHeight].reverse());
    const timeTicks = timeScale.ticks(tickCounts);

    return (
      <ScrollView style={styles.container}>
        <T heading>Challenge</T>
        <T>1. ticks should be beatuiful number (multiply by 2, 5, 10)</T>
        <T>2. the interval of grid line should be equally distributed</T>

        <Image source={{ uri: 'https://raw.githubusercontent.com/chunghe/React-Native-Stock-Chart/1478e64a8494d56ff2baf4518261c7c84e67916f/app/assets/chart.png' }} style={{ width: null, height: 300 }} resizeMode="contain" />
        <Image source={{ uri: 'https://raw.githubusercontent.com/chunghe/React-Native-Stock-Chart/1478e64a8494d56ff2baf4518261c7c84e67916f/app/assets/better.png' }} style={{ width: null, height: 300 }} resizeMode="contain" />

        <T heading>d3Array.ticks(start, end, count)</T>
        <T>Returns an array of approximately count + 1 uniformly-spaced, nicely-rounded values between start and stop (inclusive). Each value is a power of ten multiplied by 1, 2 or 5. See also tickStep and linear.ticks.</T>
        <T>Ticks are inclusive in the sense that they may include the specified start and stop values if (and only if) they are exact, nicely-rounded values consistent with the inferred step. More formally, each returned tick t satisfies start ≤ t and t ≤ stop.</T>

        <T>highest price: {lowestPrice}, lowest price: {highestPrice}, generating {tickCounts} points: {`[${priceTicks.join(', ')}]`}</T>

        <Code>
        {`
  import * as d3Array from 'd3-array';

  const priceTicks = d3Array.ticks(
    lowestPrice,
    highestPrice,
    tickCounts
  );
        `}
        </Code>
        <Svg height={defaultStockChartHeight} width={deviceWidth}>
          <Path d={areaFunction(ticks)} fill="rgb(209, 237, 255, 0.85)" />
          <Path d={lineFunction(ticks)} stroke="rgb(0, 102, 221, 0.75)" fill="none" />
          {
            priceTicks.map(t => {
              return (
                <G key={t}>
                  <Text
                    x={deviceWidth - 5}
                    y={priceScale(t)}
                    textAnchor="end"
                    fill="#999"
                    key={t}
                  >
                    {`${t}`}
                  </Text>
                  <Path d={`M0 ${priceScale(t)} ${deviceWidth} ${priceScale(t)}`} stroke="#999" strokeDasharray="2,2" />
                </G>
                );
            })
          }
        </Svg>
        <Code>
          {`
  {
    priceTicks.map(t => {
      return (
        <G key={t}>
          <Text
            x={deviceWidth - 5}
            y={priceScale(t)}
            textAnchor="end"
            fill="#999"
          >
            {\`\${t}\`}
          </Text>
          <Path
            d={\`M0 \${priceScale(t)} \${deviceWidth} \${priceScale(t)}\`}
            stroke="#999"
            strokeDasharray="2,2"
          />
        </G>
        );
    })
  }
          `}
        </Code>
        <T>exclusivePriceTicks</T>
        <Svg height={defaultStockChartHeight} width={deviceWidth}>
          <Path d={areaFunction(ticks)} fill="rgb(209, 237, 255, 0.85)" />
          <Path d={lineFunction(ticks)} stroke="rgb(0, 102, 221, 0.75)" fill="none" />
          {
            adjustPriceTicks.map(t => {
              return (
                <G key={t}>
                  <Text
                    x={deviceWidth - 5}
                    y={adjustPriceScale(t) - 15}
                    textAnchor="end"
                    fill="#999"
                    key={t}
                  >
                    {`${t}`}
                  </Text>
                  <Path d={`M0 ${adjustPriceScale(t)} ${deviceWidth} ${adjustPriceScale(t)}`} stroke="#999" strokeDasharray="2,2" />
                </G>
                );
            })
          }
        </Svg>
        <T>Calculate timeScale and timeTicks</T>
        <Svg height={defaultStockChartHeight + bottomAxisHeight} width={deviceWidth}>
          <Path d={areaFunction(ticks)} fill="rgb(209, 237, 255, 0.85)" />
          <Path d={lineFunction(ticks)} stroke="rgb(0, 102, 221, 0.75)" fill="none" />
          {
            adjustPriceTicks.map(t => {
              return (
                <G key={t}>
                  <Text
                    key={t}
                    x={deviceWidth - 5}
                    y={adjustPriceScale(t) - 15}
                    textAnchor="end"
                    fill="#999"
                  >
                    {`${t}`}
                  </Text>
                  <Path d={`M0 ${adjustPriceScale(t)} ${deviceWidth} ${adjustPriceScale(t)}`} stroke="#999" strokeDasharray="2,2" />
                </G>
                );
            })
          }
          <G y={defaultStockChartHeight} fill="red">
          {
            timeTicks.map(t => {
              return (
                <G key={t}>
                  <Text
                    key={t}
                    x={timeScale(t)}
                    y={0}
                    textAnchor="middle"
                    fill="#999"
                  >
                    {`${this.formatTime(t)}`}
                  </Text>
                  <Rect x={timeScale(t)} y={0} width="1" height="5" fill="#999" />
                </G>
                );
            })
          }
          </G>
        </Svg>
        <Code>
        {`

  const timeTicks = timeScale.ticks(tickCounts);

  {
    timeTicks.map(t => {
      return (
        <G key={t}>
          <Text
            key={t}
            x={timeScale(t)}
            y={0}
            textAnchor="middle"
            fill="#999"
          >
            {\`\${this.formatTime(t)}\`}
          </Text>
          <Rect x={timeScale(t)} y={0} width="1" height="5" fill="#999" />
        </G>
        );
    })
  }
        `}
        </Code>
      </ScrollView>
    );
  }
Exemple #23
0
 function computeXScale() {
   const xScale =
     domain.x[0] instanceof Date ? d3ScaleTime() : d3ScaleLinear();
   return xScale.range([xPadding, size.width - xPadding]).domain(domain.x);
 }
Exemple #24
0
		getDefaultProps: () => ({
			data: [{x: new Date('2015-01-01T00:00:00Z'), y: 1}],
			xScale: d3Scale.scaleTime(),
			yScale: d3Scale.scaleLinear(),
		}),
Exemple #25
0
 *
 * Copyright 2016-present, Raphaël Benitte.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
import { scaleLinear, scalePoint, scaleTime } from 'd3-scale'

export const linearXScale = scaleLinear()
    .range([0, 280])
    .domain([0, 80])
linearXScale.type = 'linear'

export const linearYScale = scaleLinear()
    .range([160, 0])
    .domain([0, 35])
linearYScale.type = 'linear'

export const pointXScale = scalePoint()
    .range([0, 280])
    .domain(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'])
pointXScale.type = 'point'

const timeScaleStart = new Date(2019, 0, 1, 0, 0, 0, 0)
const timeScaleEnd = new Date(2020, 0, 1, 0, 0, 0, 0)

export const timeXScale = scaleTime()
    .range([0, 280])
    .domain([timeScaleStart, timeScaleEnd])
timeXScale.type = 'time'
Exemple #26
0
/**
 *
 *  data: []
 *  series: [ {label, x, y} ]
 *
 */
export default function timeline() {

	var _id = 'timeline_line_' + Date.now();

	/**
	 * Style stuff
	 */

	// Margin between the main plot group and the svg border
	var _margin = { top: 10, right: 10, bottom: 20, left: 40 };

	// Height and width of the SVG element
	var _height = 100, _width = 600;

	// Render the grid?
	var _displayOptions = {
		xGrid: false,
		yGrid: false,
		pointEvents: false // value, values, series, custom (falsey is off)
	};


	/**
	 * Configuration of accessors to data
	 */

	// Various configuration functions
	var _fn = {
		valueX: function(d) { return d[0]; },

		markerValueX: function(d) { return d[0]; },
		markerLabel: function(d) { return d[1]; },

		pointRadius: function() { return 2; }
	};


	/**
	 * Extents, Axes, and Scales
	 */

	// Extent configuration for x and y dimensions of plot
	var now = Date.now();
	var _extent = {
		x: extent({
			defaultValue: [ now - 60000 * 5, now ],
			getValue: function(d, i) { return _fn.valueX(d, i); }
		}),
		y: extent({
			filter: function(d, i) {
				var x = _fn.valueX(d, i);
				return x >= _scale.x.domain()[0] && x <= _scale.x.domain()[1];
			}
		})
	};
	var _multiExtent = multiExtent();


	// Default scales for x and y dimensions
	var _scale = {
		x: d3_scaleTime(),
		y: d3_scaleLinear()
	};

	// Default Axis definitions
	var _axis = {
		x: d3_axisBottom().scale(_scale.x),
		y: d3_axisLeft().ticks(3).scale(_scale.y),

		xGrid: d3_axisBottom().tickFormat('').tickSizeOuter(0).scale(_scale.x),
		yGrid: d3_axisLeft().tickFormat('').tickSizeOuter(0).ticks(3).scale(_scale.y)
	};


	/**
	 * Generators
	 */

	var _line = d3_line()
		.x(function(d, i) { return _scale.x(_fn.valueX(d, i)); });

	var _area = d3_area()
		.x(function(d, i) { return _scale.x(_fn.valueX(d, i)); });

	// Voronoi that we'll use for hovers
	var _voronoi = d3_voronoi()
		.x(function(d, i) {
			return _scale.x(d.x, i);
		})
		.y(function(d, i) {
			return _scale.y(d.y, i);
		});


	/**
	 * Brush and Events
	 */

	// Brush Management
	var _brush = timelineBrush({ brush: d3_brushX(), scale: _scale.x });
	_brush.dispatch()
		.on('end', function() {
			updateBrush();
			_dispatch.call('brushEnd', this, getBrush());
		})
		.on('start', function() {
			updateBrush();
			_dispatch.call('brushStart', this, getBrush());
		})
		.on('brush', function() {
			updateBrush();
			_dispatch.call('brush', this, getBrush());
		});

	// The dispatch object and all events
	var _dispatch = d3_dispatch(
		'brush', 'brushStart', 'brushEnd',
		'markerClick', 'markerMouseover', 'markerMouseout',
		'pointMouseover', 'pointMouseout', 'pointClick');


	/**
	 * Keep track of commonly access DOM elements
	 */

	// Storage for commonly used DOM elements
	var _element = {
		svg: undefined,

		g: {
			container: undefined,
			plots: undefined,
			plotBrushes: undefined,
			points: undefined,
			voronoi: undefined,

			xAxis: undefined,
			yAxis: undefined,
			xAxisGrid: undefined,
			yAxisGrid: undefined,

			markers: undefined,
			brush: undefined
		},

		plotClipPath: undefined,
		plotBrushClipPath: undefined,
		markerClipPath: undefined
	};


	/**
	 * Data and Series and Markers
	 */

	// The main data array
	var _data = [];

	// The definition of the series to draw
	var _series = [];

	// Markers data
	var _markers = [];

	/**
	 * Explodes the data into an array with one point per unique point
	 * in the data (according to the series).
	 *
	 * I.e.,
	 *
	 * data: [{ x: 0, y1: 1, y2: 2}]
	 * series: [
	 *     { key: 's1', getValue: function(d) { return d.y1; } },
	 *     { key: 's2', getValue: function(d) { return d.y2; } }
	 * ]
	 *
	 * ==>
	 *
	 * [
	 *     { x: 0, y: 1, series: { key: 's1', ... }, data: { x: 0, y1: 1, y2: 2 },
	 *     { x: 0, y: 2, series: { key: 's2', ... }, data: { x: 0, y1: 1, y2: 2 },
	 * ]
	 *
	 * @param series
	 * @param data
	 */
	function getVoronoiData(series, data, getXValue) {
		var toReturn = [];

		// Loop over each series
		series.forEach(function(s, i) {

			// Convert the data to x/y series
			toReturn = toReturn.concat(data.map(function(d, ii) {
				return {
					x: getXValue(d, ii),
					y: s.getValue(d, ii),
					series: s,
					data: d
				};
			}));

		});

		return toReturn;
	}

	function highlightValues(hovered) {
		if (null != hovered) {

			var join = _element.g.points.selectAll('circle')
				.data(_series.map(function(d) {
					return {
						x: _fn.valueX(hovered.data),
						y: d.getValue(hovered.data),
						category: d.category
					};
				}));
			var enter = join.enter().append('circle');
			var update = join.selectAll('circle');

			enter.merge(update)
				.attr('class', function(d, i) { return d.category; })
				.attr('cx', function(d, i) { return _scale.x(d.x); })
				.attr('cy', function(d, i) { return _scale.y(d.y); })
				.attr('r', 3);
		}
		else {
			_element.g.points.selectAll('circle').remove();
		}
	}

	function highlightValue(hovered) {}
	function highlightSeries(hovered) {}


	function onPointMouseover(d, i) {

		var pointAction = _displayOptions.pointEvents;
		if('value' === pointAction) {
			highlightValue(d.data);
		}
		else if('values' === pointAction) {
			highlightValues(d.data);
		}
		else if('series' === pointAction) {
			highlightSeries(d.data);
		}

		_dispatch.call('pointMouseover', this, d.data, i);
	}

	function onPointMouseout(d, i) {

		var pointAction = _displayOptions.pointEvents;
		if('value' === pointAction) {
			highlightValue();
		}
		else if('values' === pointAction) {
			highlightValues();
		}
		else if('series' === pointAction) {
			highlightSeries();
		}

		_dispatch.call('pointMouseout', this, d.data, i);
	}

	function onPointClick(d, i) {
		_dispatch.call('pointClick', this, d.data, i);
	}

	/**
	 * Get the current brush state in terms of the x data domain, in ms epoch time
	 */
	function getBrush() {

		// Try to get the node from the brush group selection
		var node = (null != _element.g.brush)? _element.g.brush.node() : null;

		// Get the current brush selection
		return _brush.getSelection(node);

	}

	function getBrushSelection() {

		// Try to get the node from the brush group selection
		var node = (null != _element.g.brush)? _element.g.brush.node() : null;

		// Get the current brush selection
		return _brush.getBrushSelection(node);

	}

	function getBrushHandlePath(d) {
		var w = 8, h = 12, ch = 4;
		var y = (_scale.y.range()[0] / 2) + (h / 2);

		return 'M' + (w / 2) + ' ' + y + ' c 0 ' + ch + ', ' + (-w) + ' ' + ch + ', ' + (-w) + ' 0 v0 ' + (-h) + ' c 0 ' + (-ch) + ', ' + w + ' ' + (-ch) + ', ' + w + ' 0 Z M0' + ' ' + y + ' v' + (-h);
	}

	/**
	 * Set the current brush state in terms of the x data domain
	 * @param v The new value of the brush
	 *
	 */
	function setBrush(v) {
		_brush.setSelection(_element.g.brush, v);
	}


	/**
	 * Update the state of the brush (as part of redrawing everything)
	 *
	 * The purpose of this function is to update the state of the brush to reflect changes
	 * to the rest of the chart as part of a normal update/redraw cycle. When the x extent
	 * changes, the brush needs to move to stay correctly aligned with the x axis. Normally,
	 * we are only updating the drawn position of the brush, so the brushSelection doesn't
	 * actually change. However, if the change results in the brush extending partially or
	 * wholly outside of the x extent, we might have to clip or clear the brush, which will
	 * result in brush change events being propagated.
	 *
	 * @param previousExtent The previous state of the brush extent. Must be provided to
	 *        accurately determine the extent of the brush in terms of the x data domain
	 */
	function updateBrush(previousExtent) {

		// If there was no previous extent, then there is no brush to update
		if (null != previousExtent) {

			// Derive the overall plot extent from the collection of series
			var plotExtent = _extent.x.getExtent(_data);

			if(null != plotExtent && Array.isArray(plotExtent) && plotExtent.length == 2) {

				// Clip extent by the full extent of the plot (this is in case we've slipped off the visible plot)
				var newExtent = [ Math.max(plotExtent[0], previousExtent[0]), Math.min(plotExtent[1], previousExtent[1]) ];
				setBrush(newExtent);

			}
			else {
				// There is no plot/data so just clear the brush
				setBrush(undefined);
			}
		}

		_element.g.brush
			.style('display', (_brush.enabled())? 'unset' : 'none')
			.call(_brush.brush());


		/*
		 * Update the clip path for the brush plot
		 */
		var brushExtent = getBrushSelection();
		if (null != brushExtent) {

			var height = _scale.y.range()[0];

			// Update the brush clip path
			_element.plotBrushClipPath
				.attr('transform', 'translate(' + brushExtent[0] + ', -1)')
				.attr('width', Math.max(0, brushExtent[1] - brushExtent[0]))
				.attr('height', Math.max(0, height) + 2);

			// Create/Update the handles
			var handleJoin = _element.g.brush
				.selectAll('.resize-handle').data([ { type: 'w' }, { type: 'e' } ]);

			var handleEnter = handleJoin.enter().append('g')
				.attr('class', 'resize-handle')
				.attr('cursor', 'ew-resize');

			handleEnter.append('path').attr('class', 'handle-line');
			handleEnter.append('path').attr('class', 'handle-grip');

			var merge = handleEnter.merge(handleJoin);
			merge.attr('transform', function(d, i) { return 'translate(' + brushExtent[i] + ', 0)'; });
			merge.select('.handle-line')
				.attr('d', 'M0 ' + height + ' v' + (-height));
			merge.select('.handle-grip')
				.attr('d', getBrushHandlePath);

		}
		else {

			// Empty the clip path
			_element.plotBrushClipPath
				.attr('transform', 'translate(-1, -1)')
				.attr('width', 0)
				.attr('height', 0);

			// Remove the handles
			_element.g.brush.selectAll('.resize-handle')
				.remove();

		}

	}


	function updateAxes() {
		if (null != _axis.x) {
			_element.g.xAxis.call(_axis.x);
		}
		if (null != _axis.xGrid && _displayOptions.xGrid) {
			_element.g.xAxisGrid.call(_axis.xGrid);
		}
		if (null != _axis.y) {
			_element.g.yAxis.call(_axis.y);
		}
		if (null != _axis.yGrid && _displayOptions.yGrid) {
			_element.g.yAxisGrid.call(_axis.yGrid);
		}
	}


	function updatePlots() {

		// Join
		var plotJoin = _element.g.plots
			.selectAll('.plot')
			.data(_series, function(d) { return d.key; });

		// Enter
		var plotEnter = plotJoin.enter().append('g').attr('class', 'plot');

		var lineEnter = plotEnter.append('g').append('path')
			.attr('class', function(d) { return ((d.category)? d.category : '') + ' line'; });
		var areaEnter = plotEnter.append('g').append('path')
			.attr('class', function(d) { return ((d.category)? d.category : '') + ' area'; });

		var lineUpdate = plotJoin.select('.line');
		var areaUpdate = plotJoin.select('.area');

		// Enter + Update
		lineEnter.merge(lineUpdate)
			.attr('d', function(series) {
				return _line.y(function (d, i) { return _scale.y(series.getValue(d, i)); })(_data);
			});

		areaEnter.merge(areaUpdate)
			.attr('d', function(series) {
				return _area
					.y0(_scale.y.range()[0])
					.y1(function (d, i) { return _scale.y(series.getValue(d, i)); })(_data);
			});


		// Remove the previous voronoi
		_element.g.voronoi.selectAll('path').remove();

		if (_displayOptions.pointEvents) {

			// check range against width
			var extent = _scale.x.domain();
			var voronoiData = getVoronoiData(_series, _data, _fn.valueX)
				.filter(function(d) {
					// Filter out points that are outside of the extent
					return (extent[0] <= d.x && d.x <= extent[1]);
				});

			// Filter out paths that are null
			voronoiData  = _voronoi.polygons(voronoiData)
				.filter(function (d) { return (null != d); });

			// Draw the voronoi overlay polygons
			_element.g.voronoi.selectAll('path').data(voronoiData).enter().append('path')
				.attr('d', function (d) { return (null != d) ? 'M' + d.join('L') + 'Z' : null; })
				.on('mouseover', onPointMouseover)
				.on('mouseout', onPointMouseout)
				.on('click', onPointClick);

		}

		// Exit
		var plotExit = plotJoin.exit();
		plotExit.remove();
	}

	function updatePlotBrushes() {

		// Join
		var plotJoin = _element.g.plotBrushes
			.selectAll('.plot-brush')
			.data((_brush.enabled())? _series : [], function(d) { return d.key; });

		// Enter
		var plotEnter = plotJoin.enter().append('g').attr('class', 'plot plot-brush');

		var lineEnter = plotEnter.append('g').append('path')
			.attr('class', function(d) { return ((d.category)? d.category : '') + ' line'; });
		var areaEnter = plotEnter.append('g').append('path')
			.attr('class', function(d) { return ((d.category)? d.category : '') + ' area'; });

		var lineUpdate = plotJoin.select('.line');
		var areaUpdate = plotJoin.select('.area');

		// Enter + Update
		lineEnter.merge(lineUpdate)
			.attr('d', function(series) {
				return _line.y(function (d, i) { return _scale.y(series.getValue(d, i)); })(_data);
			});

		areaEnter.merge(areaUpdate)
			.attr('d', function(series) {
				return _area
					.y0(_scale.y.range()[0])
					.y1(function (d, i) { return _scale.y(series.getValue(d, i)); })(_data);
			});

		// Exit
		var plotExit = plotJoin.exit();
		plotExit.remove();

	}

	function updateMarkers() {

		// Join
		var markerJoin = _element.g.markers
			.selectAll('.marker')
			.data(_markers, _fn.markerValueX);

		// Enter
		var markerEnter = markerJoin.enter().append('g')
			.attr('class', 'marker')
			.on('mouseover', function(d, i) { _dispatch.call('markerMouseover', this, d, i); })
			.on('mouseout', function(d, i) { _dispatch.call('markerMouseout', this, d, i); })
			.on('click', function(d, i) { _dispatch.call('markerClick', this, d, i); });

		var lineEnter = markerEnter.append('line');
		var textEnter = markerEnter.append('text');

		lineEnter
			.attr('y1', function(d) { return _scale.y.range()[1]; })
			.attr('y2', function(d) { return _scale.y.range()[0]; });

		textEnter
			.attr('dy', '0em')
			.attr('y', -3)
			.attr('text-anchor', 'middle')
			.text(_fn.markerLabel);

		// Enter + Update
		var lineUpdate = markerJoin.select('line');
		var textUpdate = markerJoin.select('text');

		lineEnter.merge(lineUpdate)
			.attr('x1', function(d, i) { return _scale.x(_fn.markerValueX(d, i)); })
			.attr('x2', function(d, i) { return _scale.x(_fn.markerValueX(d)); });

		textEnter.merge(textUpdate)
			.attr('x', function(d, i) { return _scale.x(_fn.markerValueX(d)); });

		// Exit
		markerJoin.exit().remove();

	}


	// Chart create/init method
	function _instance() {}


	/**
	 * Initialize the chart (only called once). Performs all initial chart creation/setup
	 *
	 * @param container The container element to which to apply the chart
	 * @returns {_instance} Instance of the chart
	 */
	_instance.init = function(container) {

		// Create a container div
		_element.div = container.append('div').attr('class', 'sentio timeline');

		// Create the SVG element
		_element.svg = _element.div.append('svg');

		// Add the defs and add the clip path definition
		var defs = _element.svg.append('defs');
		_element.plotBrushClipPath = defs.append('clipPath').attr('id', 'plotBrush_' + _id).append('rect');
		_element.plotClipPath = defs.append('clipPath').attr('id', 'plot_' + _id).append('rect');
		_element.markerClipPath = defs.append('clipPath').attr('id', 'marker_' + _id).append('rect');

		// Append a container for everything
		_element.g.container = _element.svg.append('g');

		// Append the grid
		_element.g.grid = _element.g.container.append('g').attr('class', 'grid');
		_element.g.xAxisGrid = _element.g.grid.append('g').attr('class', 'x');
		_element.g.yAxisGrid = _element.g.grid.append('g').attr('class', 'y');

		// Append the path group (which will have the clip path and the line path
		_element.g.plots = _element.g.container.append('g').attr('class', 'plots');
		_element.g.plots.attr('clip-path', 'url(#plot_' + _id + ')');

		// Append the path group (which will have the clip path and the line path
		_element.g.plotBrushes = _element.g.container.append('g').attr('class', 'plot-brushes');
		_element.g.plotBrushes.attr('clip-path', 'url(#plotBrush_' + _id + ')');
		_element.g.plotBrushHandles = _element.g.container.append('g').attr('class', 'plot-brush-handles');

		// Append groups for the axes
		_element.g.axes = _element.g.container.append('g').attr('class', 'axis');
		_element.g.xAxis = _element.g.axes.append('g').attr('class', 'x');
		_element.g.yAxis = _element.g.axes.append('g').attr('class', 'y');

		// Append a group for the voronoi and the points
		_element.g.points = _element.g.container.append('g').attr('class', 'points');
		_element.g.points.attr('clip-path', 'url(#marker_' + _id + ')');
		_element.g.voronoi = _element.g.container.append('g').attr('class', 'voronoi');


		// Append a group for the markers
		_element.g.markers = _element.g.container.append('g').attr('class', 'markers');
		_element.g.markers.attr('clip-path', 'url(#marker_' + _id + ')');

		// Add the brush element
		_element.g.brush = _element.g.container.append('g').attr('class', 'x brush');

		_instance.resize();

		return _instance;
	};

	/*
	 * Set the data to drive the chart
	 */
	_instance.data = function(v) {
		if (!arguments.length) { return _data; }
		_data = (null != v)? v : [];

		return _instance;
	};

	/*
	 * Define the series to show on the chart
	 */
	_instance.series = function(v) {
		if (!arguments.length) { return _series; }
		_series = (null != v)? v : [];

		return _instance;
	};

	/*
	 * Set the markers data
	 */
	_instance.markers = function(v) {
		if (!arguments.length) { return _markers; }
		_markers = (null != v)? v : [];
		return _instance;
	};

	/*
	 * Updates all the elements that depend on the size of the various components
	 */
	_instance.resize = function() {

		// Need to grab the brush extent before we change anything
		var brushSelection = getBrush();


		// Resize the SVG Pane
		_element.svg.attr('width', _width).attr('height', _height);

		// Update the margins on the main draw group
		_element.g.container.attr('transform', 'translate(' + _margin.left + ',' + _margin.top + ')');


		// Resize Scales
		_scale.x.range([ 0, Math.max(0, _width - _margin.left - _margin.right) ]);
		_scale.y.range([ Math.max(0, _height - _margin.top - _margin.bottom), 0 ]);


		/**
		 * Resize clip paths
		 */

		// Plot Brush clip path is only the plot pane
		_element.plotBrushClipPath
			.attr('transform', 'translate(-1, -1)')
			.attr('width', Math.max(0, _scale.x.range()[1]) + 2)
			.attr('height', Math.max(0, _scale.y.range()[0]) + 2);

		// Plot clip path is only the plot pane
		_element.plotClipPath
			.attr('transform', 'translate(-1, -1)')
			.attr('width', Math.max(0, _scale.x.range()[1]) + 2)
			.attr('height', Math.max(0, _scale.y.range()[0]) + 2);

		// Marker clip path includes top margin by default
		_element.markerClipPath
			.attr('transform', 'translate(0, -' + _margin.top + ')')
			.attr('width', Math.max(0, _width - _margin.left - _margin.right))
			.attr('height', Math.max(0, _height - _margin.bottom));

		// Resize the clip extent of the plot
		_voronoi.extent([
			[ 0, 0 ],
			[ _width - _margin.left - _margin.right, _height - _margin.top - _margin.bottom ]
		]);


		/**
		 * Update axis and grids
		 */

		// Reset axis and grid positions
		_element.g.xAxis.attr('transform', 'translate(0,' + _scale.y.range()[0] + ')');
		_element.g.xAxisGrid.attr('transform', 'translate(0,' + _scale.y.range()[0] + ')');


		// Resize the x grid ticks
		if (_displayOptions.xGrid) {
			_axis.xGrid.tickSizeInner(-(_height - _margin.top - _margin.bottom));
		}
		else {
			_axis.xGrid.tickSizeInner(0);
		}

		// Resize the y grid ticks
		if (_displayOptions.yGrid) {
			_axis.yGrid.tickSizeInner(-(_width - _margin.left - _margin.right));
		}
		else {
			_axis.yGrid.tickSizeInner(0);
		}


		/**
		 * Update the brush
		 */

		// Resize and position the brush g element
		_element.g.brush.selectAll('rect')
			.attr('y', -1).attr('x', 0)
			.attr('width', _scale.x.range()[1])
			.attr('height', _scale.y.range()[0] + 2);

		// Resize the brush
		_brush.brush()
			.extent([ [ 0, 0 ], [ _scale.x.range()[1], _scale.y.range()[0] + 2 ] ]);

		updateBrush(brushSelection);


		return _instance;
	};


	/*
	 * Redraw the graphic
	 */
	_instance.redraw = function() {

		// Need to grab the brush extent before we change anything
		var brushSelection = getBrush();

		// Update the x domain (to the latest time window)
		_scale.x.domain(_extent.x.getExtent(_data));

		// Update the y domain (based on configuration and data)
		_scale.y.domain(_multiExtent.extent(_extent.y).series(_series).getExtent(_data));

		// Update the plot elements
		updateAxes();
		updatePlots();
		updatePlotBrushes();
		updateMarkers();
		updateBrush(brushSelection);

		return _instance;
	};


	// Basic Getters/Setters
	_instance.width = function(v) {
		if (!arguments.length) { return _width; }
		_width = v;
		return _instance;
	};
	_instance.height = function(v) {
		if (!arguments.length) { return _height; }
		_height = v;
		return _instance;
	};
	_instance.margin = function(v) {
		if (!arguments.length) { return _margin; }
		_margin = v;
		return _instance;
	};
	_instance.showXGrid = function(v) {
		if (!arguments.length) { return _displayOptions.xGrid; }
		_displayOptions.xGrid = v;
		return _instance;
	};
	_instance.showYGrid = function(v) {
		if (!arguments.length) { return _displayOptions.yGrid; }
		_displayOptions.yGrid = v;
		return _instance;
	};
	_instance.showGrid = function(v) {
		_displayOptions.xGrid = _displayOptions.yGrid = v;
		return _instance;
	};
	_instance.pointEvents = function(v) {
		if (!arguments.length) { return _displayOptions.pointEvents; }
		_displayOptions.pointEvents = v;
		return _instance;
	};

	_instance.curve = function(v) {
		if (!arguments.length) { return _line.curve(); }
		_line.curve(v);
		_area.curve(v);
		return _instance;
	};

	_instance.xAxis = function(v) {
		if (!arguments.length) { return _axis.x; }
		_axis.x = v;
		return _instance;
	};
	_instance.xGridAxis = function(v) {
		if (!arguments.length) { return _axis.xGrid; }
		_axis.xGrid = v;
		return _instance;
	};
	_instance.yAxis = function(v) {
		if (!arguments.length) { return _axis.y; }
		_axis.y = v;
		return _instance;
	};
	_instance.yGridAxis = function(v) {
		if (!arguments.length) { return _axis.yGrid; }
		_axis.yGrid = v;
		return _instance;
	};
	_instance.xScale = function(v) {
		if (!arguments.length) { return _scale.x; }
		_scale.x = v;
		if (null != _axis.x) {
			_axis.x.scale(v);
		}
		if (null != _axis.xGrid) {
			_axis.xGrid.scale(v);
		}
		if (null != _brush) {
			_brush.scale(v);
		}
		return _instance;
	};
	_instance.yScale = function(v) {
		if (!arguments.length) { return _scale.y; }
		_scale.y = v;
		if (null != _axis.y) {
			_axis.y.scale(v);
		}
		if (null != _axis.yGrid) {
			_axis.yGrid.scale(v);
		}
		return _instance;
	};
	_instance.xValue = function(v) {
		if (!arguments.length) { return _fn.valueX; }
		_fn.valueX = v;
		return _instance;
	};
	_instance.yExtent = function(v) {
		if (!arguments.length) { return _extent.y; }
		_extent.y = v;
		return _instance;
	};
	_instance.xExtent = function(v) {
		if (!arguments.length) { return _extent.x; }
		_extent.x = v;
		return _instance;
	};

	_instance.markerXValue = function(v) {
		if (!arguments.length) { return _fn.markerValueX; }
		_fn.markerValueX = v;
		return _instance;
	};
	_instance.markerLabel = function(v) {
		if (!arguments.length) { return _fn.markerLabel; }
		_fn.markerLabel = v;
		return _instance;
	};

	_instance.dispatch = function(v) {
		if (!arguments.length) { return _dispatch; }
		return _instance;
	};

	_instance.brush = function(v) {
		if (!arguments.length) { return _brush.enabled(); }
		_brush.enabled(v);
		return _instance;
	};
	_instance.setBrush = function(v) {
		setBrush(v);
		return _instance;
	};
	_instance.getBrush = function() {
		return getBrush();
	};

	return _instance;
}
Exemple #27
0
export const semioticLineChart = (
  data: Array<Object>,
  schema: Object,
  options: Object
) => {
  let lineData;

  const {
    chart,
    selectedMetrics,
    lineType,
    metrics,
    primaryKey,
    colors
  } = options;

  const { timeseriesSort } = chart;

  const sortType =
    timeseriesSort === "array-order"
      ? "integer"
      : schema.fields.find(field => field.name === timeseriesSort).type;

  const formatting =
    sortType === "datetime"
      ? tickValue => tickValue.toLocaleString().split(",")[0]
      : numeralFormatting;

  const xScale = sortType === "datetime" ? scaleTime() : scaleLinear();

  lineData = metrics
    .map((metric, index) => {
      const metricData =
        timeseriesSort === "array-order"
          ? data
          : data.sort(
              (datapointA, datapointB) =>
                datapointA[timeseriesSort] - datapointB[timeseriesSort]
            );
      return {
        color: colors[index % colors.length],
        label: metric.name,
        type: metric.type,
        coordinates: metricData.map((datapoint, datapointValue) => ({
          value: datapoint[metric.name],
          x:
            timeseriesSort === "array-order"
              ? datapointValue
              : datapoint[timeseriesSort],
          label: metric.name,
          color: colors[index % colors.length],
          originalData: datapoint
        }))
      };
    })
    .filter(
      metric =>
        selectedMetrics.length === 0 ||
        selectedMetrics.find(selectedMetric => selectedMetric === metric.label)
    );

  return {
    lineType: { type: lineType, interpolator: curveMonotoneX },
    lines: lineData,
    xScaleType: xScale,
    renderKey: (line: Object, index: number) => {
      return line.coordinates
        ? `line-${line.label}`
        : `linepoint=${line.label}-${index}`;
    },
    lineStyle: (line: Object) => ({
      fill: lineType === "line" ? "none" : line.color,
      stroke: line.color,
      fillOpacity: 0.75
    }),
    pointStyle: (point: Object) => {
      return {
        fill: point.color,
        fillOpacity: 0.75
      };
    },
    axes: [
      { orient: "left", tickFormat: numeralFormatting },
      {
        orient: "bottom",
        ticks: 5,
        tickFormat: (tickValue: any) => {
          const label = formatting(tickValue);
          const rotation = label.length > 4 ? "45" : "0";
          const textAnchor = label.length > 4 ? "start" : "middle";
          return (
            <text transform={`rotate(${rotation})`} textAnchor={textAnchor}>
              {label}
            </text>
          );
        }
      }
    ],
    hoverAnnotation: true,
    xAccessor: "x",
    yAccessor: "value",
    showLinePoints: lineType === "line",
    margin: {
      top: 20,
      right: 200,
      bottom: sortType === "datetime" ? 80 : 40,
      left: 50
    },
    legend: {
      title: "Legend",
      position: "right",
      width: 200,
      legendGroups: [
        {
          label: "",
          styleFn: (legendItem: Object) => ({ fill: legendItem.color }),
          items: lineData
        }
      ]
    },
    tooltipContent: (hoveredDatapoint: Object) => {
      return (
        <TooltipContent x={hoveredDatapoint.x} y={hoveredDatapoint.y}>
          <p>
            {hoveredDatapoint.parentLine && hoveredDatapoint.parentLine.label}
          </p>
          <p>
            {(hoveredDatapoint.value &&
              hoveredDatapoint.value.toLocaleString()) ||
              hoveredDatapoint.value}
          </p>
          <p>
            {timeseriesSort}: {formatting(hoveredDatapoint.x)}
          </p>
          {primaryKey.map((pkey, index) => (
            <p key={`key-${index}`}>
              {pkey}:{" "}
              {(hoveredDatapoint.originalData[pkey].toString &&
                hoveredDatapoint.originalData[pkey].toString()) ||
                hoveredDatapoint.originalData[pkey]}
            </p>
          ))}
        </TooltipContent>
      );
    }
  };
};
 getXScale = (availableWidth /*: number */, flatData /*: Array<Point> */) =>
   scaleTime()
     .domain(extent(flatData, d => d.x))
     .range([0, availableWidth])
     .clamp(true);
Exemple #29
0
function Chart(node, initial) {
  var margin = [10, 10, 30, 20]
  var width = node.offsetWidth - margin[1] - margin[3]
  var height = node.offsetHeight - margin[0] - margin[2]
  var now = Date.now()
  var data = initial || []
  var maxY = 10
  var hasNewData = false

  var x = scale.scaleTime()
    .domain([now - 60000, now])
    .range([0, width])

  var y = scale.scaleLinear()
    .domain([0, maxY])
    .range([height, 0])

  var line = shape.line()
    .x(function(d) {
      return x(d.key)
    })
    .y(function(d) {
      return y(d.value)
    })

  var xAxis = axis.axisBottom(x)
    .ticks(time.timeSecond.every(5))
    .tickFormat(timeFormat.timeFormat('%I:%M:%S'))

  var yAxis = axis.axisLeft(y)
    .ticks(5)
    .tickSizeInner(-width)

  var svg = selection.select(node)
    .append('svg')
      .attr('width', width + margin[1] + margin[3])
      .attr('height', height + margin[0] + margin[2])
    .append('g')
      .attr('transform', 'translate(' + margin[3] + ',' + margin[0] + ')')

  var yAxisSvg = svg.append('g')
    .attr('class', 'y-axis')
    .attr('transform', 'translate(0,0)')
    .call(yAxis)

  var xAxisSvg = svg.append('g')
    .attr('class', 'x-axis')
    .attr('transform', 'translate(0,' + height + ')')
    .call(xAxis)

  var lineSvg = svg.append('path')
    .datum(data)
    .attr('class', 'line')
    .attr('d', line)

  function tick() {
    now = Date.now()
    x.domain([now - 60000, now])

    if(!hasNewData) {
      data.push({key: now, value: 0})
    } else {
      hasNewData = false
    }

    lineSvg
      .attr('d', line)
      .transition()
      .duration(1000)
      .ease(ease.easeLinear)
      .attr('transform', 'translate(' + x(now - 60000) + ')')
      .on('end', tick)

    xAxisSvg
      .call(xAxis)

    yAxisSvg
      .transition()
      .call(yAxis)

    for(var i in data) {
      if(data[i].key < now - 60000) {
        data.splice(i, 1)
      } else {
        break
      }
    }
  }

  this.start = tick
  this.insert = function(d) {
    if(d.value > maxY) {
      maxY = d.value
      y.domain([0, maxY])
    }

    for(var i = data.length - 1; i > 0; i--) {
      if(d.key > data[i].key) {
        data.splice(i + 1, 0, d)
        break
      }
    }

    hasNewData = true
  }
}
	render() {
		const { type, data, width, ratio } = this.props;

		const xAccessor = d => d.date;
		const start = xAccessor(last(data));
		const end = xAccessor(data[Math.max(0, data.length - 150)]);
		const xExtents = [start, end];

		return (
			<ChartCanvas height={400}
					ratio={ratio}
					width={width}
					margin={{ left: 80, right: 80, top: 10, bottom: 30 }}
					type={type}
					seriesName="MSFT"
					data={data}
					xScale={scaleTime()}
					xAccessor={xAccessor}
					xExtents={xExtents}>
				<Chart id={2}
						yExtents={[d => d.volume]}
						height={150} origin={(w, h) => [0, h - 150]}>
					<YAxis axisAt="left" orient="left" ticks={5} tickFormat={format(".0s")}/>

					<MouseCoordinateY
						at="left"
						orient="left"
						displayFormat={format(".4s")} />

					<BarSeries yAccessor={d => d.volume} fill={d => d.close > d.open ? "#6BA583" : "#FF0000"} />

					<CurrentCoordinate yAccessor={d => d.volume} fill="#9B0A47" />

					<EdgeIndicator itemType="first" orient="left" edgeAt="left"
						yAccessor={d => d.volume} displayFormat={format(".4s")} fill="#0F0F0F"/>
					<EdgeIndicator itemType="last" orient="right" edgeAt="right"
						yAccessor={d => d.volume} displayFormat={format(".4s")} fill="#0F0F0F"/>
				</Chart>
				<Chart id={1}
						yExtents={[d => [d.high, d.low]]}
						padding={{ top: 40, bottom: 20 }}>
					<XAxis axisAt="bottom" orient="bottom"/>
					<YAxis axisAt="right" orient="right" ticks={5} />

					<MouseCoordinateX
						rectWidth={60}
						at="bottom"
						orient="bottom"
						displayFormat={timeFormat("%H:%M:%S")} />
					<MouseCoordinateY
						at="right"
						orient="right"
						displayFormat={format(".2f")} />

					<CandlestickSeries />

					<EdgeIndicator itemType="last" orient="right" edgeAt="right"
						yAccessor={d => d.close} fill={d => d.close > d.open ? "#6BA583" : "#FF0000"}/>

					<OHLCTooltip origin={[-40, 0]} xDisplayFormat={timeFormat("%Y-%m-%d %H:%M:%S")}/>
				</Chart>
				<CrossHairCursor />
			</ChartCanvas>
		);
	}