-
Notifications
You must be signed in to change notification settings - Fork 145
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
เกิดอยากลองทำ Heatmap ขึ้นมา #246
base: master
Are you sure you want to change the base?
Conversation
Currently, the distraction between the symbols of a party has no candidate in that zone or only few vote received. I would suggest 2 things:
|
@heytitle I agree with 1), but for 2) I think for zones whose candidate received almost no votes, I prefer to make it obvious. Otherwise, it becomes hard to distinguish between having some votes and having almost no votes. i.e. it’s more obvious which region doesn’t support which party. |
ok, that's reasonable. let's see how it looks. |
return match && match.complete ? 1 : 0.5 | ||
opacity(data) { | ||
const match = this.dataLookup[data.id] | ||
return match && match.opacity != null ? match.opacity : 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The opacity computation should happen within this file, not the container. To separate visual encoding logic from data.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm… I have to disagree (quite strongly) on this one, because as the ElectionMap gets re-used to display multiple aspects of the election result, the code in this file will become increasingly complex, trying to account for all the visualizations.
e.g.
if (!match) {
return 1
}
if (match.displayMode === 'heatmap') {
if (match.areaStatus === 'winning') {
return scaleLinear(/* ... */)
} else if (match.areaStatus === 'sent') {
return scaleLinear(/* ... */)
// (...so on...)
Not only the logic, but the interface would become more complex. for example, here’s what would happen to IMapZone to make election map have all the data necessary to compute the opacity:
interface IMapZone {
id: number
partyId: number
complete: boolean
show: boolean
+ displayMode: 'heatmap' | 'winningCandidate'
+ areaStatus?: 'winning' | 'sent' | null
+ displayedCandidateScore?: number
+ winningCandidateScore?: number
+ totalVotersCount?: number
}
Creating more visualizations would require editing code at 2 places — modifying the container to pass the required data to ElectionMap, and modifying the ElectionMap (which is 400+ lines already) to be able to make sense of that data. This shows a clear violation of the open-closed principle…
In fact, in my opinion, the ElectionMap would be easier to use and understand how did it become a more dumb component. i.e. it receives only what it needs to know to display it, and displays it, e.g.:
interface IMapZone {
id: number
- partyId: number
- complete: boolean
- show: boolean
+ fill: 'solid' | 'striped'
+ color: string
+ opacity: number
}
This would allow more visualization to be created just by creating a new container with little to no modification to the ElectionMap.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fair enough. agree with passing the computed encoding can be more convenient as the logic for computation is complex.
Another pattern that many d3-based components use is to pass
function color(datum){}
function opacity(datum){}
function color(datum){}
as separate arguments
(When usingd3kit
these can be passed viaoptions
)
and passdata
with all the necessary fields.
The pros of this approach are
- The
datum
contains all raw fields. If there is any new encoding channel (e.g.strokeColor
) that does not require new field in the raw data, only new encoding function is needed. - The
datum
is usually passed to the event listener, e.g.mouseenter
that tooltip use. Having the raw data item can be convenient for display instead of having to perform lookup again. - matches general visualization mental model that map data to visual encoding channel.
The cons
- precomputing the channels can be more optimized if they shared computation logic.
let opacity = 1 | ||
let winningPartyId = "nope" | ||
let complete = false | ||
const interpolate = (value, min = 0, max = 1) => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use d3.scaleLinear
or more specifically import { scaleLinear } from 'd3-scale'
will save you many lines.
Also worth considering scaleQuantize
instead to provide steps of colors instead of continuous range.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your suggestion — will do that.
เอาไว้ดูภาพรวมคะแนนเสียงของแต่ละพรรค