Spaces:
Running
Running
/* | |
* Licensed to the Apache Software Foundation (ASF) under one | |
* or more contributor license agreements. See the NOTICE file | |
* distributed with this work for additional information | |
* regarding copyright ownership. The ASF licenses this file | |
* to you under the Apache License, Version 2.0 (the | |
* "License"); you may not use this file except in compliance | |
* with the License. You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, | |
* software distributed under the License is distributed on an | |
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
* KIND, either express or implied. See the License for the | |
* specific language governing permissions and limitations | |
* under the License. | |
*/ | |
import * as zrUtil from 'zrender/src/core/util'; | |
import ExtensionAPI from '../core/ExtensionAPI'; | |
import GlobalModel from '../model/Global'; | |
import Model from '../model/Model'; | |
import SeriesModel from '../model/Series'; | |
import {makeInner} from '../util/model'; | |
import {Dictionary, DecalObject, InnerDecalObject, AriaOption} from '../util/types'; | |
import {LocaleOption} from '../core/locale'; | |
import { getDecalFromPalette } from '../model/mixin/palette'; | |
import type {TitleOption} from '../component/title/install'; | |
const DEFAULT_OPTION: AriaOption = { | |
label: { | |
enabled: true | |
}, | |
decal: { | |
show: false | |
} | |
}; | |
const inner = makeInner<{scope: object}, SeriesModel>(); | |
const decalPaletteScope: Dictionary<DecalObject> = {}; | |
type SeriesTypes = keyof LocaleOption['series']['typeNames']; | |
export default function ariaVisual(ecModel: GlobalModel, api: ExtensionAPI) { | |
const ariaModel: Model<AriaOption> = ecModel.getModel('aria'); | |
// See "area enabled" detection code in `GlobalModel.ts`. | |
if (!ariaModel.get('enabled')) { | |
return; | |
} | |
const defaultOption = zrUtil.clone(DEFAULT_OPTION); | |
zrUtil.merge(defaultOption.label, ecModel.getLocaleModel().get('aria'), false); | |
zrUtil.merge(ariaModel.option, defaultOption, false); | |
setDecal(); | |
setLabel(); | |
function setDecal() { | |
const decalModel = ariaModel.getModel('decal'); | |
const useDecal = decalModel.get('show'); | |
if (useDecal) { | |
// Each type of series use one scope. | |
// Pie and funnel are using different scopes. | |
const paletteScopeGroupByType = zrUtil.createHashMap<object>(); | |
ecModel.eachSeries((seriesModel: SeriesModel) => { | |
if (seriesModel.isColorBySeries()) { | |
return; | |
} | |
let decalScope = paletteScopeGroupByType.get(seriesModel.type); | |
if (!decalScope) { | |
decalScope = {}; | |
paletteScopeGroupByType.set(seriesModel.type, decalScope); | |
} | |
inner(seriesModel).scope = decalScope; | |
}); | |
ecModel.eachRawSeries((seriesModel: SeriesModel) => { | |
if (ecModel.isSeriesFiltered(seriesModel)) { | |
return; | |
} | |
if (zrUtil.isFunction(seriesModel.enableAriaDecal)) { | |
// Let series define how to use decal palette on data | |
seriesModel.enableAriaDecal(); | |
return; | |
} | |
const data = seriesModel.getData(); | |
if (!seriesModel.isColorBySeries()) { | |
const dataAll = seriesModel.getRawData(); | |
const idxMap: Dictionary<number> = {}; | |
const decalScope = inner(seriesModel).scope; | |
data.each(function (idx) { | |
const rawIdx = data.getRawIndex(idx); | |
idxMap[rawIdx] = idx; | |
}); | |
const dataCount = dataAll.count(); | |
dataAll.each(rawIdx => { | |
const idx = idxMap[rawIdx]; | |
const name = dataAll.getName(rawIdx) || (rawIdx + ''); | |
const paletteDecal = getDecalFromPalette( | |
seriesModel.ecModel, | |
name, | |
decalScope, | |
dataCount | |
); | |
const specifiedDecal = data.getItemVisual(idx, 'decal'); | |
data.setItemVisual(idx, 'decal', mergeDecal(specifiedDecal, paletteDecal)); | |
}); | |
} | |
else { | |
const paletteDecal = getDecalFromPalette( | |
seriesModel.ecModel, | |
seriesModel.name, | |
decalPaletteScope, | |
ecModel.getSeriesCount() | |
); | |
const specifiedDecal = data.getVisual('decal'); | |
data.setVisual('decal', mergeDecal(specifiedDecal, paletteDecal)); | |
} | |
function mergeDecal(specifiedDecal: DecalObject, paletteDecal: DecalObject): DecalObject { | |
// Merge decal from palette to decal from itemStyle. | |
// User do not need to specify all of the decal props. | |
const resultDecal = specifiedDecal | |
? zrUtil.extend(zrUtil.extend({}, paletteDecal), specifiedDecal) | |
: paletteDecal; | |
(resultDecal as InnerDecalObject).dirty = true; | |
return resultDecal; | |
} | |
}); | |
} | |
} | |
function setLabel() { | |
const labelLocale = ecModel.getLocaleModel().get('aria'); | |
const labelModel = ariaModel.getModel('label'); | |
labelModel.option = zrUtil.defaults(labelModel.option, labelLocale); | |
if (!labelModel.get('enabled')) { | |
return; | |
} | |
const dom = api.getZr().dom; | |
if (labelModel.get('description')) { | |
dom.setAttribute('aria-label', labelModel.get('description')); | |
return; | |
} | |
const seriesCnt = ecModel.getSeriesCount(); | |
const maxDataCnt = labelModel.get(['data', 'maxCount']) || 10; | |
const maxSeriesCnt = labelModel.get(['series', 'maxCount']) || 10; | |
const displaySeriesCnt = Math.min(seriesCnt, maxSeriesCnt); | |
let ariaLabel; | |
if (seriesCnt < 1) { | |
// No series, no aria label | |
return; | |
} | |
else { | |
const title = getTitle(); | |
if (title) { | |
const withTitle = labelModel.get(['general', 'withTitle']); | |
ariaLabel = replace(withTitle, { | |
title: title | |
}); | |
} | |
else { | |
ariaLabel = labelModel.get(['general', 'withoutTitle']); | |
} | |
const seriesLabels: string[] = []; | |
const prefix = seriesCnt > 1 | |
? labelModel.get(['series', 'multiple', 'prefix']) | |
: labelModel.get(['series', 'single', 'prefix']); | |
ariaLabel += replace(prefix, { seriesCount: seriesCnt }); | |
ecModel.eachSeries(function (seriesModel, idx) { | |
if (idx < displaySeriesCnt) { | |
let seriesLabel; | |
const seriesName = seriesModel.get('name'); | |
const withName = seriesName ? 'withName' : 'withoutName'; | |
seriesLabel = seriesCnt > 1 | |
? labelModel.get(['series', 'multiple', withName]) | |
: labelModel.get(['series', 'single', withName]); | |
seriesLabel = replace(seriesLabel, { | |
seriesId: seriesModel.seriesIndex, | |
seriesName: seriesModel.get('name'), | |
seriesType: getSeriesTypeName(seriesModel.subType as SeriesTypes) | |
}); | |
const data = seriesModel.getData(); | |
if (data.count() > maxDataCnt) { | |
// Show part of data | |
const partialLabel = labelModel.get(['data', 'partialData']); | |
seriesLabel += replace(partialLabel, { | |
displayCnt: maxDataCnt | |
}); | |
} | |
else { | |
seriesLabel += labelModel.get(['data', 'allData']); | |
} | |
const middleSeparator = labelModel.get(['data', 'separator', 'middle']); | |
const endSeparator = labelModel.get(['data', 'separator', 'end']); | |
const dataLabels = []; | |
for (let i = 0; i < data.count(); i++) { | |
if (i < maxDataCnt) { | |
const name = data.getName(i); | |
const value = data.getValues(i); | |
const dataLabel = labelModel.get(['data', name ? 'withName' : 'withoutName']); | |
dataLabels.push( | |
replace(dataLabel, { | |
name: name, | |
value: value.join(middleSeparator) | |
}) | |
); | |
} | |
} | |
seriesLabel += dataLabels.join(middleSeparator) + endSeparator; | |
seriesLabels.push(seriesLabel); | |
} | |
}); | |
const separatorModel = labelModel.getModel(['series', 'multiple', 'separator']); | |
const middleSeparator = separatorModel.get('middle'); | |
const endSeparator = separatorModel.get('end'); | |
ariaLabel += seriesLabels.join(middleSeparator) + endSeparator; | |
dom.setAttribute('aria-label', ariaLabel); | |
} | |
} | |
function replace(str: string, keyValues: object) { | |
if (!zrUtil.isString(str)) { | |
return str; | |
} | |
let result = str; | |
zrUtil.each(keyValues, function (value: string, key: string) { | |
result = result.replace( | |
new RegExp('\\{\\s*' + key + '\\s*\\}', 'g'), | |
value | |
); | |
}); | |
return result; | |
} | |
function getTitle() { | |
let title = ecModel.get('title') as TitleOption | TitleOption[]; | |
if (title && (title as TitleOption[]).length) { | |
title = (title as TitleOption[])[0]; | |
} | |
return title && (title as TitleOption).text; | |
} | |
function getSeriesTypeName(type: SeriesTypes) { | |
const typeNames = ecModel.getLocaleModel().get(['series', 'typeNames']); | |
return typeNames[type] || typeNames.chart; | |
} | |
} | |