| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- // 提取自 https://github.com/fnando/sparkline
- function getY(max: number, height: number, diff: number, value: number) {
- return parseFloat((height - (value * height / max) + diff).toFixed(2))
- }
- function removeChildren(svg: any) {
- [...svg.querySelectorAll('*')].forEach(element => svg.removeChild(element))
- }
- function defaultFetch(entry: any) {
- return entry.value
- }
- function buildElement(tag: string, attrs: Record<string, any>) {
- const element = document.createElementNS('http://www.w3.org/2000/svg', tag)
- for (const name in attrs) {
- element.setAttribute(name, attrs[name])
- }
- return element
- }
- export function sparkline(svg: any, entries: any, options: any) {
- removeChildren(svg)
- if (entries.length <= 1) {
- return
- }
- options = options || {}
- if (typeof (entries[0]) === 'number') {
- entries = entries.map((entry: any) => {
- return { value: entry }
- })
- }
- // This function will be called whenever the mouse moves
- // over the SVG. You can use it to render something like a
- // tooltip.
- const onmousemove = options.onmousemove
- // This function will be called whenever the mouse leaves
- // the SVG area. You can use it to hide the tooltip.
- const onmouseout = options.onmouseout
- // Should we run in interactive mode? If yes, this will handle the
- // cursor and spot position when moving the mouse.
- const interactive = ('interactive' in options) ? options.interactive : !!onmousemove
- // Define how big should be the spot area.
- const spotRadius = options.spotRadius || 2
- const spotDiameter = spotRadius * 2
- // Define how wide should be the cursor area.
- const cursorWidth = options.cursorWidth || 2
- // Get the stroke width; this is used to compute the
- // rendering offset.
- const strokeWidth = parseFloat(svg.attributes['stroke-width'].value)
- // By default, db must be formatted as an array of numbers or
- // an array of objects with the value key (like `[{value: 1}]`).
- // You can set a custom function to return db for a different
- // db structure.
- const fetch = options.fetch || defaultFetch
- // Retrieve only values, easing the find for the maximum value.
- const values = entries.map((entry: any) => fetch(entry))
- // The rendering width will account for the spot size.
- const width = parseFloat(svg.attributes.width.value) - spotDiameter * 2
- // Get the SVG element's full height.
- // This is used
- const fullHeight = parseFloat(svg.attributes.height.value)
- // The rendering height accounts for stroke width and spot size.
- const height = fullHeight - (strokeWidth * 2) - spotDiameter
- // The maximum value. This is used to calculate the Y coord of
- // each sparkline datapoint.
- const max = Math.max(...values)
- // Some arbitrary value to remove the cursor and spot out of
- // the viewing canvas.
- const offscreen = -1000
- // Cache the last item index.
- const lastItemIndex = values.length - 1
- // Calculate the X coord base step.
- const offset = width / lastItemIndex
- // Hold all datapoints, which is whatever we got as the entry plus
- // x/y coords and the index.
- const datapoints: any[] = []
- // Hold the line coordinates.
- const pathY = getY(max, height, strokeWidth + spotRadius, values[0])
- let pathCoords = `M${spotDiameter} ${pathY}`
- values.forEach((value: number, index: number) => {
- const x = index * offset + spotDiameter
- const y = getY(max, height, strokeWidth + spotRadius, value)
- datapoints.push(Object.assign({}, entries[index], {
- index,
- x,
- y,
- }))
- pathCoords += ` L ${x} ${y}`
- })
- const path = buildElement('path', {
- class: 'sparkline--line',
- d: pathCoords,
- fill: 'none',
- })
- const fillCoords = `${pathCoords} V ${fullHeight} L ${spotDiameter} ${fullHeight} Z`
- const fill = buildElement('path', {
- class: 'sparkline--fill',
- d: fillCoords,
- stroke: 'none',
- })
- svg.appendChild(fill)
- svg.appendChild(path)
- if (!interactive) {
- return
- }
- const cursor = buildElement('line', {
- 'class': 'sparkline--cursor',
- 'x1': offscreen,
- 'x2': offscreen,
- 'y1': 0,
- 'y2': fullHeight,
- 'stroke-width': cursorWidth,
- })
- const spot = buildElement('circle', {
- class: 'sparkline--spot',
- cx: offscreen,
- cy: offscreen,
- r: spotRadius,
- })
- svg.appendChild(cursor)
- svg.appendChild(spot)
- const interactionLayer = buildElement('rect', {
- width: svg.attributes.width.value,
- height: svg.attributes.height.value,
- style: 'fill: transparent; stroke: transparent',
- class: 'sparkline--interaction-layer',
- })
- svg.appendChild(interactionLayer)
- interactionLayer.addEventListener('mouseout', (event: MouseEvent) => {
- cursor.setAttribute('x1', offscreen.toString())
- cursor.setAttribute('x2', offscreen.toString())
- spot.setAttribute('cx', offscreen.toString())
- if (onmouseout) {
- onmouseout(event)
- }
- })
- interactionLayer.addEventListener('mousemove', (event: MouseEvent) => {
- const mouseX = event.offsetX
- let nextDataPoint = datapoints.find((entry) => {
- return entry.x >= mouseX
- })
- if (!nextDataPoint) {
- nextDataPoint = datapoints[lastItemIndex]
- }
- const previousDataPoint = datapoints[datapoints.indexOf(nextDataPoint) - 1]
- let currentDataPoint
- let halfway
- if (previousDataPoint) {
- halfway = previousDataPoint.x + ((nextDataPoint.x - previousDataPoint.x) / 2)
- currentDataPoint = mouseX >= halfway ? nextDataPoint : previousDataPoint
- }
- else {
- currentDataPoint = nextDataPoint
- }
- const x = currentDataPoint.x
- const y = currentDataPoint.y
- spot.setAttribute('cx', x)
- spot.setAttribute('cy', y)
- cursor.setAttribute('x1', x)
- cursor.setAttribute('x2', x)
- if (onmousemove) {
- onmousemove(event, currentDataPoint)
- }
- })
- }
- export default sparkline
|