| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431 |
- import { doUpdate } from '../class/updater.class'
- import { deepClone } from '@jiaminghi/c-render/lib/plugin/util'
- import { legendConfig } from '../config'
- import { deepMerge, mulAdd } from '../util'
- export function legend (chart, option = {}) {
- let { legend } = option
- if (legend) {
- legend = deepMerge(deepClone(legendConfig, true), legend)
- legend = initLegendData(legend)
-
- legend = filterInvalidData(legend, option, chart)
-
- legend = calcLegendTextWidth(legend, chart)
-
- legend = calcLegendPosition(legend, chart)
- legend = [legend]
- } else {
- legend = []
- }
- doUpdate({
- chart,
- series: legend,
- key: 'legendIcon',
- getGraphConfig: getIconConfig
- })
- doUpdate({
- chart,
- series: legend,
- key: 'legendText',
- getGraphConfig: getTextConfig
- })
- }
- function initLegendData (legend) {
- const { data } = legend
- legend.data = data.map(item => {
- const itemType = typeof item
- if (itemType === 'string') {
- return { name: item }
- } else if (itemType === 'object') {
- return item
- }
- return { name: '' }
- })
- return legend
- }
- function filterInvalidData (legend, option, chart) {
- const { series } = option
- let { legendStatus } = chart
- const data = legend.data.filter(item => {
- const { name } = item
- const result = series.find(({ name: sn }) => name === sn)
- if (!result) return false
- if (!item.color) item.color = result.color
- if (!item.icon) item.icon = result.type
- return item
- })
- if (!legendStatus || legendStatus.length !== legend.data.length) legendStatus = new Array(legend.data.length).fill(true)
- data.forEach((item, i) => (item.status = legendStatus[i]))
- legend.data = data
- chart.legendStatus = legendStatus
- return legend
- }
- function calcLegendTextWidth (legend, chart) {
- const { ctx } = chart.render
- const { data, textStyle, textUnselectedStyle } = legend
- data.forEach(item => {
- const { status, name } = item
- item.textWidth = getTextWidth(ctx, name, status ? textStyle : textUnselectedStyle)
- })
- return legend
- }
- function getTextWidth (ctx, text, style) {
- ctx.font = getFontConfig(style)
- return ctx.measureText(text).width
- }
- function getFontConfig (style) {
- const { fontFamily, fontSize } = style
- return `${fontSize}px ${fontFamily}`
- }
- function calcLegendPosition (legend, chart) {
- const { orient } = legend
- if (orient === 'vertical') {
- calcVerticalPosition(legend, chart)
- } else {
- calcHorizontalPosition(legend, chart)
- }
-
- return legend
- }
- function calcHorizontalPosition (legend, chart) {
- const { iconHeight, itemGap } = legend
- const lines = calcDefaultHorizontalPosition(legend, chart)
- const xOffsets = lines.map(line => getHorizontalXOffset(line, legend, chart))
- const yOffset = getHorizontalYOffset(legend, chart)
- const align = { textAlign: 'left', textBaseline: 'middle' }
- lines.forEach((line, i) => line.forEach(item => {
- const { iconPosition, textPosition } = item
- let xOffset = xOffsets[i]
- const realYOffset = yOffset + i * (itemGap + iconHeight)
- item.iconPosition = mergeOffset(iconPosition, [xOffset, realYOffset])
- item.textPosition = mergeOffset(textPosition, [xOffset, realYOffset])
- item.align = align
- })
- )
- }
- function calcDefaultHorizontalPosition (legend, chart) {
- const { data, iconWidth } = legend
- const w = chart.render.area[0]
- let startIndex = 0
- const lines = [[]]
- data.forEach((item, i) => {
- let beforeWidth = getBeforeWidth(startIndex, i, legend)
- const endXPos = beforeWidth + iconWidth + 5 + item.textWidth
- if (endXPos >= w) {
- startIndex = i
- beforeWidth = getBeforeWidth(startIndex, i, legend)
- lines.push([])
- }
- item.iconPosition = [beforeWidth, 0]
- item.textPosition = [beforeWidth + iconWidth + 5, 0]
- lines.slice(-1)[0].push(item)
- })
- return lines
- }
- function getBeforeWidth (startIndex, currentIndex, legend) {
- const { data, iconWidth, itemGap } = legend
- const beforeItem = data.slice(startIndex, currentIndex)
- return mulAdd(beforeItem.map(({ textWidth }) => textWidth)) + (currentIndex - startIndex) * (itemGap + 5 + iconWidth)
- }
- function getHorizontalXOffset (data, legend, chart) {
- let { left, right, iconWidth, itemGap } = legend
- const w = chart.render.area[0]
- const dataNum = data.length
- const allWidth = mulAdd(data.map(({ textWidth }) => textWidth)) + dataNum * (5 + iconWidth) + (dataNum - 1) * itemGap
- const horizontal = [left, right].findIndex(pos => pos !== 'auto')
- if (horizontal === -1) {
- return (w - allWidth) / 2
- } else if (horizontal === 0) {
- if (typeof left === 'number') return left
- return parseInt(left) / 100 * w
- } else {
- if (typeof right !== 'number') right = parseInt(right) / 100 * w
- return w - (allWidth + right)
- }
- }
- function getHorizontalYOffset (legend, chart) {
- let { top, bottom, iconHeight } = legend
- const h = chart.render.area[1]
- const vertical = [top, bottom].findIndex(pos => pos !== 'auto')
- const halfIconHeight = iconHeight / 2
- if (vertical === -1) {
- const { y, h: height } = chart.gridArea
- return y + height + 45 - halfIconHeight
- } else if (vertical === 0) {
- if (typeof top === 'number') return top - halfIconHeight
- return parseInt(top) / 100 * h - halfIconHeight
- } else {
- if (typeof bottom !== 'number') bottom = parseInt(bottom) / 100 * h
- return h - bottom - halfIconHeight
- }
- }
- function mergeOffset ([x, y], [ox, oy]) {
- return [x + ox, y + oy]
- }
- function calcVerticalPosition (legend, chart) {
- const [isRight, xOffset] = getVerticalXOffset(legend, chart)
- const yOffset = getVerticalYOffset(legend, chart)
- calcDefaultVerticalPosition(legend, isRight)
- let align = { textAlign: 'left', textBaseline: 'middle' }
- legend.data.forEach(item => {
- const { textPosition, iconPosition } = item
- item.textPosition = mergeOffset(textPosition, [xOffset, yOffset])
- item.iconPosition = mergeOffset(iconPosition, [xOffset, yOffset])
- item.align = align
- })
- }
- function getVerticalXOffset (legend, chart) {
- const { left, right } = legend
- const w = chart.render.area[0]
- const horizontal = [left, right].findIndex(pos => pos !== 'auto')
- if (horizontal === -1) {
- return [
- true,
- w - 10
- ]
- } else {
- let offset = [left, right][horizontal]
- if (typeof offset !== 'number') offset = parseInt(offset) / 100 * w
- return [
- Boolean(horizontal),
- offset
- ]
- }
- }
- function getVerticalYOffset (legend, chart) {
- const { iconHeight, itemGap, data, top, bottom } = legend
- const h = chart.render.area[1]
- const dataNum = data.length
- const allHeight = dataNum * iconHeight + (dataNum - 1) * itemGap
- const vertical = [top, bottom].findIndex(pos => pos !== 'auto')
- if (vertical === -1) {
- return (h - allHeight) / 2
- } else {
- let offset = [top, bottom][vertical]
- if (typeof offset !== 'number') offset = parseInt(offset) / 100 * h
- if (vertical === 1) offset = h - offset - allHeight
- return offset
- }
- }
- function calcDefaultVerticalPosition (legend, isRight) {
- const { data, iconWidth, iconHeight, itemGap } = legend
- const halfIconHeight = iconHeight / 2
- data.forEach((item, i) => {
- const { textWidth } = item
- const yPos = (iconHeight + itemGap) * i + halfIconHeight
- const iconXPos = isRight ? (0 - iconWidth) : 0
- const textXpos = isRight ? (iconXPos - 5 - textWidth) : (iconWidth + 5)
- item.iconPosition = [iconXPos, yPos]
- item.textPosition = [textXpos, yPos]
- })
- }
- function getIconConfig (legendItem, updater) {
- const { data, selectAble, animationCurve, animationFrame, rLevel } = legendItem
- return data.map((item, i) => ({
- name: item.icon === 'line' ? 'lineIcon' : 'rect',
- index: rLevel,
- visible: legendItem.show,
- hover: selectAble,
- click: selectAble,
- animationCurve,
- animationFrame,
- shape: getIconShape(legendItem, i),
- style: getIconStyle(legendItem, i),
- click: createClickCallBack(legendItem, i, updater)
- }))
- }
- function getIconShape (legendItem, i) {
- const { data, iconWidth, iconHeight } = legendItem
- const [x, y] = data[i].iconPosition
- const halfIconHeight = iconHeight / 2
- return {
- x,
- y: y - halfIconHeight,
- w: iconWidth,
- h: iconHeight
- }
- }
- function getIconStyle (legendItem, i) {
- const { data, iconStyle, iconUnselectedStyle } = legendItem
- const { status, color } = data[i]
- const style = status ? iconStyle : iconUnselectedStyle
- return deepMerge({ fill: color }, style)
- }
- function getTextConfig (legendItem, updater) {
- const { data, selectAble, animationCurve, animationFrame, rLevel } = legendItem
- return data.map((foo, i) => ({
- name: 'text',
- index: rLevel,
- visible: legendItem.show,
- hover: selectAble,
- animationCurve,
- animationFrame,
- hoverRect: getTextHoverRect(legendItem, i),
- shape: getTextShape(legendItem, i),
- style: getTextStyle(legendItem, i),
- click: createClickCallBack(legendItem, i, updater)
- }))
- }
- function getTextShape (legendItem, i) {
- const { textPosition, name } = legendItem.data[i]
- return {
- content: name,
- position: textPosition
- }
- }
- function getTextStyle (legendItem, i) {
- const { textStyle, textUnselectedStyle } = legendItem
- const { status, align } = legendItem.data[i]
- const style = status ? textStyle : textUnselectedStyle
- return deepMerge(deepClone(style, true), align)
- }
- function getTextHoverRect (legendItem, i) {
- const { textStyle, textUnselectedStyle } = legendItem
- const { status, textPosition: [x, y], textWidth } = legendItem.data[i]
- const style = status ? textStyle : textUnselectedStyle
- const { fontSize } = style
- return [x, y - (fontSize / 2), textWidth, fontSize]
- }
- function createClickCallBack (legendItem, index, updater) {
- const { name } = legendItem.data[index]
- return () => {
- const { legendStatus, option } = updater.chart
- const status = !legendStatus[index]
- const change = option.series.find(({ name: sn }) => sn === name)
- change.show = status
- legendStatus[index] = status
- updater.chart.setOption(option)
- }
- }
|