netscout-mthorn opened a new pull request, #21646:
URL: https://github.com/apache/echarts/pull/21646
## Brief Information
This pull request is in the type of:
- [ ] bug fixing
- [x] new feature
- [ ] others
### What does this PR do?
Adds a `logMapping: 'asinh' | 'symlog'` option to the `log` axis type,
enabling logarithmic
axes that correctly handle zero and negative values via symmetric transform
functions.
### Fixed issues
- #15558: Display negative value in Logarithmic axis
- #17459: [Feature] Support negative and zero when axis type is 'log'
## Details
### Before: What was the problem?
The `log` axis type requires all data values to be strictly positive. Zero
and negative
values are filtered out by `getFilter()` and rejected by `setExtent()`.
There is no
supported way to display mixed-sign data or data containing zero on a
logarithmic axis.
Previous attempts (#16547, #20872) addressed this by auto-detecting negative
data or
implementing a "negative log" transform, but reviewer `100pah` identified
these
approaches as fragile and mathematically unsound for the general case
(see the "Summary of Reviewer Feedback" section in #20872).
### After: How does it behave after the fixing?
A new axis option `logMapping` is added to the `log` axis type:
```ts
yAxis: {
type: 'log',
logBase: 10, // unchanged, controls tick spacing
logMapping: 'asinh' // new, enables the asinh transform (or 'symlog')
}
```
When `logMapping: 'asinh'` is set:
- The forward transform uses `asinh(x / a0) * a0` instead of `log_b(x)`,
where `a0`
is controlled by `logLinearWidth` (default `1`, matching d3/matplotlib
conventions).
This function is defined for all real numbers, passes smoothly through
zero, and
behaves like `log` for large `|x|`.
- Zero and negative data values are accepted and rendered correctly.
- The axis is odd-symmetric: `f(-x) = -f(x)`, so negative data produces a
mirrored
log-like axis.
- Tick marks at `0, +-a0, +-b*a0, +-b^2*a0, ...` (where `a0 =
logLinearWidth`, `b = logBase`) appear naturally.
When `logMapping: 'symlog'` is set:
- The forward transform uses `sign(x) * ln(1 + |x|/C)`, where `C` is the
linear
threshold controlled by `logLinearWidth` (same option, default `1`). This
is the symlog transform familiar to matplotlib and d3 users.
- Behaviour is otherwise identical to `'asinh'`: zero and negatives accepted,
odd-symmetric axis, tick marks at `0, +-C, +-b*C, ...`
When `logMapping` is absent or `'none'`, behaviour is **identical to
before**. The
standard log transform is used and all existing constraints apply. There are
no
breaking changes.
<img width="951" height="344" alt="Screenshot 2026-06-10 at 9 43 53 AM"
src="https://github.com/user-attachments/assets/ae8041d0-c3be-411f-b79a-5b9e21fdb080"
/>
## Document Info
One of the following should be checked.
- [ ] This PR doesn't relate to document changes
- [x] The document should be updated later
- [ ] The document changes have been made in apache/echarts-doc#xxx
## Misc
### Security Checking
- [ ] This PR uses security-sensitive Web APIs.
### ZRender Changes
- [ ] This PR depends on ZRender changes (ecomfe/zrender#xxx).
### Related test cases or examples to use the new APIs
- `test/log-mapping.html`: visual test covering positive-only, mixed-sign,
all-negative,
logBase:2, and symlog comparison scenarios
- `test/ut/spec/scale/log.test.ts`: Jest unit tests covering standard log
(regression), `logMapping: 'asinh'`, and `logMapping: 'symlog'` behaviour
### Merging options
- [x] Please squash the commits into a single one when merging.
### Other information
**Design note: `asinh` and `symlog`**
Both transforms are provided as equals. Neither is preferred over the other
in this PR.
The right choice depends on the user's data and familiarity with each
transform.
`asinh` (`asinh(x / a0) * a0`):
- Infinitely differentiable everywhere including zero, with no curvature
kink.
- Named in the ECharts `scaleMapper.ts` source as an anticipated extension.
- `Math.asinh` / `Math.sinh` are ES2015 built-ins with no polyfill needed.
`symlog` (`sign(x) * ln(1 + |x|/C)`):
- Familiar to users of matplotlib's `SymmetricalLogScale` and d3's
`scaleSymlog`.
- The linear threshold `C` (`logLinearWidth`) has a more concrete geometric
meaning
than `a0` for users who want explicit control over the transition point.
- `Math.log1p` / `Math.expm1` are ES2015 built-ins with no polyfill needed.
Both reviewer `100pah` (in the review of #20872) and matplotlib name both
transforms
as valid solutions to the negative/zero log problem. Both use the same
`logLinearWidth`
parameter and the same tick generation strategy.
**Design note: `logMapping: 'none'` as the explicit default**
The type is `'none' | 'asinh' | 'symlog'` rather than just `'asinh' |
'symlog' | undefined`. This follows
the ECharts convention for mode-selection options: `sampling` uses `'none'`
for its
default, as does `graph.layout`.
**Relationship to #20872**
This PR supersedes #20872. It takes a different approach (opt-in
`logMapping` option
with `asinh` and `symlog` transforms) rather than the `negative log` method
implemented
there, in direct response to `100pah`'s review feedback on that PR.
### Files changed
- `src/coord/axisCommonTypes.ts`
- `src/coord/axisDefault.ts`
- `src/coord/axisHelper.ts`
- `src/coord/axisNiceTicks.ts`
- `src/coord/axisAlignTicks.ts`
- `src/scale/Log.ts`
- `src/scale/helper.ts`
- `test/ut/spec/scale/log.test.ts` (new)
- `test/log-mapping.html` (new)
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]